@stylistic/stylelint-plugin
Advanced tools
Comparing version 2.1.1 to 2.1.2
import stylelint from "stylelint" | ||
import { addNamespace } from "./utils/addNamespace.js" | ||
import rules from "./rules/index.js" | ||
import { addNamespace } from "./utils/addNamespace.js" | ||
@@ -6,0 +6,0 @@ const rulesPlugins = Object.keys(rules).map((name) => stylelint.createPlugin(addNamespace(name), rules[name])) |
import stylelint from "stylelint" | ||
import isStandardSyntaxAtRule from "../../utils/isStandardSyntaxAtRule.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isStandardSyntaxAtRule from "../../utils/isStandardSyntaxAtRule.js" | ||
@@ -23,41 +23,43 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondary, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}) | ||
function rule (primary, _secondary, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
/** @type {'lower' | 'upper'} */ | ||
const expectation = primary | ||
root.walkAtRules((atRule) => { | ||
if (!isStandardSyntaxAtRule(atRule)) { | ||
if (!validOptions) { | ||
return | ||
} | ||
const name = atRule.name | ||
/** @type {'lower' | 'upper'} */ | ||
const expectation = primary | ||
const expectedName = expectation === `lower` ? name.toLowerCase() : name.toUpperCase() | ||
root.walkAtRules((atRule) => { | ||
if (!isStandardSyntaxAtRule(atRule)) { | ||
return | ||
} | ||
if (name === expectedName) { | ||
return | ||
} | ||
const name = atRule.name | ||
if (context.fix) { | ||
atRule.name = expectedName | ||
const expectedName = expectation === `lower` ? name.toLowerCase() : name.toUpperCase() | ||
return | ||
} | ||
if (name === expectedName) { | ||
return | ||
} | ||
report({ | ||
message: messages.expected(name, expectedName), | ||
node: atRule, | ||
ruleName, | ||
result, | ||
if (context.fix) { | ||
atRule.name = expectedName | ||
return | ||
} | ||
report({ | ||
message: messages.expected(name, expectedName), | ||
node: atRule, | ||
ruleName, | ||
result, | ||
}) | ||
}) | ||
}) | ||
} | ||
} | ||
@@ -68,2 +70,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleNameSpaceChecker from "../../utils/atRuleNameSpaceChecker.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -23,3 +23,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary) => { | ||
function rule (primary) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -49,2 +49,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleNameSpaceChecker from "../../utils/atRuleNameSpaceChecker.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -24,3 +24,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondary, context) => { | ||
function rule (primary, _secondary, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -43,7 +43,9 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (atRule) => { | ||
if (typeof atRule.raws.afterName === `string`) { | ||
atRule.raws.afterName = atRule.raws.afterName.replace(/^\s*/, ` `) | ||
fix: context.fix | ||
? (atRule) => { | ||
if (typeof atRule.raws.afterName === `string`) { | ||
atRule.raws.afterName = atRule.raws.afterName.replace(/^\s*/, ` `) | ||
} | ||
} | ||
} : null, | ||
: null, | ||
}) | ||
@@ -56,2 +58,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import hasBlock from "../../utils/hasBlock.js" | ||
@@ -8,4 +10,2 @@ import isStandardSyntaxAtRule from "../../utils/isStandardSyntaxAtRule.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -28,3 +28,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondary, context) => { | ||
function rule (primary, _secondary, context) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -88,2 +88,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import hasBlock from "../../utils/hasBlock.js" | ||
@@ -7,4 +9,2 @@ import isStandardSyntaxAtRule from "../../utils/isStandardSyntaxAtRule.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,3 +27,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary) => { | ||
function rule (primary) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -72,2 +72,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import addEmptyLineAfter from "../../utils/addEmptyLineAfter.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import blockString from "../../utils/blockString.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import hasBlock from "../../utils/hasBlock.js" | ||
@@ -11,4 +13,2 @@ import hasEmptyBlock from "../../utils/hasEmptyBlock.js" | ||
import removeEmptyLinesAfter from "../../utils/removeEmptyLinesAfter.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -32,92 +32,92 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
possible: [`always-multi-line`, `never`], | ||
}, | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
except: [`after-closing-brace`], | ||
function rule (primary, secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
possible: [`always-multi-line`, `never`], | ||
}, | ||
optional: true, | ||
}, | ||
) | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
except: [`after-closing-brace`], | ||
}, | ||
optional: true, | ||
}, | ||
) | ||
if (!validOptions) { | ||
return | ||
} | ||
// Check both kinds of statements: rules and at-rules | ||
root.walkRules(check) | ||
root.walkAtRules(check) | ||
/** | ||
* @param {import('postcss').Rule | import('postcss').AtRule} statement | ||
*/ | ||
function check (statement) { | ||
// Return early if blockless or has empty block | ||
if (!hasBlock(statement) || hasEmptyBlock(statement)) { | ||
if (!validOptions) { | ||
return | ||
} | ||
// Get whitespace after ""}", ignoring extra semicolon | ||
const before = (statement.raws.after || ``).replace(/;+/, ``) | ||
// Check both kinds of statements: rules and at-rules | ||
root.walkRules(check) | ||
root.walkAtRules(check) | ||
// Calculate index | ||
const statementString = statement.toString() | ||
let index = statementString.length - 1 | ||
/** | ||
* @param {import('postcss').Rule | import('postcss').AtRule} statement | ||
*/ | ||
function check (statement) { | ||
// Return early if blockless or has empty block | ||
if (!hasBlock(statement) || hasEmptyBlock(statement)) { | ||
return | ||
} | ||
if (statementString[index - 1] === `\r`) { | ||
index -= 1 | ||
} | ||
// Get whitespace after ""}", ignoring extra semicolon | ||
const before = (statement.raws.after || ``).replace(/;+/, ``) | ||
// Set expectation | ||
const expectEmptyLineBefore = (() => { | ||
const childNodeTypes = statement.nodes.map((item) => item.type) | ||
// Calculate index | ||
const statementString = statement.toString() | ||
let index = statementString.length - 1 | ||
// Reverse the primary options if `after-closing-brace` is set | ||
if ( | ||
optionsMatches(secondaryOptions, `except`, `after-closing-brace`) && !childNodeTypes.includes(`decl`) | ||
) { | ||
return primary === `never` | ||
if (statementString[index - 1] === `\r`) { | ||
index -= 1 | ||
} | ||
return primary === `always-multi-line` && !isSingleLineString(blockString(statement)) | ||
})() | ||
// Set expectation | ||
const expectEmptyLineBefore = (() => { | ||
const childNodeTypes = statement.nodes.map((item) => item.type) | ||
// Check for at least one empty line | ||
const hasEmptyLineBefore = hasEmptyLine(before) | ||
// Reverse the primary options if `after-closing-brace` is set | ||
if (optionsMatches(secondaryOptions, `except`, `after-closing-brace`) && !childNodeTypes.includes(`decl`)) { | ||
return primary === `never` | ||
} | ||
// Return if the expectation is met | ||
if (expectEmptyLineBefore === hasEmptyLineBefore) { | ||
return | ||
} | ||
return primary === `always-multi-line` && !isSingleLineString(blockString(statement)) | ||
})() | ||
if (context.fix) { | ||
const { newline } = context | ||
// Check for at least one empty line | ||
const hasEmptyLineBefore = hasEmptyLine(before) | ||
if (typeof newline !== `string`) {return} | ||
// Return if the expectation is met | ||
if (expectEmptyLineBefore === hasEmptyLineBefore) { | ||
return | ||
} | ||
if (expectEmptyLineBefore) { | ||
addEmptyLineAfter(statement, newline) | ||
} else { | ||
removeEmptyLinesAfter(statement, newline) | ||
if (context.fix) { | ||
const { newline } = context | ||
if (typeof newline !== `string`) { return } | ||
if (expectEmptyLineBefore) { | ||
addEmptyLineAfter(statement, newline) | ||
} else { | ||
removeEmptyLinesAfter(statement, newline) | ||
} | ||
return | ||
} | ||
return | ||
const message = expectEmptyLineBefore ? messages.expected : messages.rejected | ||
report({ | ||
message, | ||
result, | ||
ruleName, | ||
node: statement, | ||
index, | ||
}) | ||
} | ||
const message = expectEmptyLineBefore ? messages.expected : messages.rejected | ||
report({ | ||
message, | ||
result, | ||
ruleName, | ||
node: statement, | ||
index, | ||
}) | ||
} | ||
@@ -129,2 +129,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import blockString from "../../utils/blockString.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import hasBlock from "../../utils/hasBlock.js" | ||
import { isString } from "../../utils/validateTypes.js" | ||
import optionsMatches from "../../utils/optionsMatches.js" | ||
import rawNodeString from "../../utils/rawNodeString.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { isString } from "../../utils/validateTypes.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -32,3 +32,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions, context) => { | ||
function rule (primary, secondaryOptions, context) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -75,5 +75,3 @@ | ||
if ( | ||
statement.type === `atrule` && optionsMatches(secondaryOptions, `ignoreAtRules`, statement.name) | ||
) { | ||
if (statement.type === `atrule` && optionsMatches(secondaryOptions, `ignoreAtRules`, statement.name)) { | ||
return | ||
@@ -89,3 +87,3 @@ } | ||
// Allow an end-of-line comment x spaces after the brace | ||
const nextNodeIsSingleLineComment = nextNode.type === `comment` && !/[^ ]/.test(nextNode.raws.before || ``) && !nextNode.toString().includes(`\n`) | ||
const nextNodeIsSingleLineComment = nextNode.type === `comment` && !(/[^ ]/).test(nextNode.raws.before || ``) && !nextNode.toString().includes(`\n`) | ||
@@ -117,3 +115,3 @@ const nodeToCheck = nextNodeIsSingleLineComment ? nextNode.next() : nextNode | ||
if (typeof nodeToCheckRaws.before !== `string`) {return} | ||
if (typeof nodeToCheckRaws.before !== `string`) { return } | ||
@@ -151,2 +149,3 @@ if (primary.startsWith(`always`)) { | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import blockString from "../../utils/blockString.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import hasBlock from "../../utils/hasBlock.js" | ||
import hasEmptyBlock from "../../utils/hasEmptyBlock.js" | ||
import isSingleLineString from "../../utils/isSingleLineString.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -28,92 +28,94 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `always-multi-line`, `never-multi-line`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `always-multi-line`, `never-multi-line`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
// Check both kinds of statements: rules and at-rules | ||
root.walkRules(check) | ||
root.walkAtRules(check) | ||
/** | ||
* @param {import('postcss').Rule | import('postcss').AtRule} statement | ||
*/ | ||
function check (statement) { | ||
// Return early if blockless or has empty block | ||
if (!hasBlock(statement) || hasEmptyBlock(statement)) { | ||
if (!validOptions) { | ||
return | ||
} | ||
// Ignore extra semicolon | ||
const after = (statement.raws.after || ``).replace(/;+/, ``) | ||
// Check both kinds of statements: rules and at-rules | ||
root.walkRules(check) | ||
root.walkAtRules(check) | ||
if (after === undefined) { | ||
return | ||
} | ||
/** | ||
* @param {import('postcss').Rule | import('postcss').AtRule} statement | ||
*/ | ||
function check (statement) { | ||
// Return early if blockless or has empty block | ||
if (!hasBlock(statement) || hasEmptyBlock(statement)) { | ||
return | ||
} | ||
const blockIsMultiLine = !isSingleLineString(blockString(statement)) | ||
const statementString = statement.toString() | ||
// Ignore extra semicolon | ||
const after = (statement.raws.after || ``).replace(/;+/, ``) | ||
let index = statementString.length - 2 | ||
if (after === undefined) { | ||
return | ||
} | ||
if (statementString[index - 1] === `\r`) { | ||
index -= 1 | ||
} | ||
const blockIsMultiLine = !isSingleLineString(blockString(statement)) | ||
const statementString = statement.toString() | ||
// We're really just checking whether a | ||
// newline *starts* the block's final space -- between | ||
// the last declaration and the closing brace. We can | ||
// ignore any other whitespace between them, because that | ||
// will be checked by the indentation rule. | ||
if (!after.startsWith(`\n`) && !after.startsWith(`\r\n`)) { | ||
if (primary === `always`) { | ||
complain(messages.expectedBefore) | ||
} else if (blockIsMultiLine && primary === `always-multi-line`) { | ||
complain(messages.expectedBeforeMultiLine) | ||
let index = statementString.length - 2 | ||
if (statementString[index - 1] === `\r`) { | ||
index -= 1 | ||
} | ||
} | ||
if (after !== `` && blockIsMultiLine && primary === `never-multi-line`) { | ||
complain(messages.rejectedBeforeMultiLine) | ||
} | ||
// We're really just checking whether a | ||
// newline *starts* the block's final space -- between | ||
// the last declaration and the closing brace. We can | ||
// ignore any other whitespace between them, because that | ||
// will be checked by the indentation rule. | ||
if (!after.startsWith(`\n`) && !after.startsWith(`\r\n`)) { | ||
if (primary === `always`) { | ||
complain(messages.expectedBefore) | ||
} else if (blockIsMultiLine && primary === `always-multi-line`) { | ||
complain(messages.expectedBeforeMultiLine) | ||
} | ||
} | ||
/** | ||
* @param {string} message | ||
*/ | ||
function complain (message) { | ||
if (context.fix) { | ||
const statementRaws = statement.raws | ||
if (after !== `` && blockIsMultiLine && primary === `never-multi-line`) { | ||
complain(messages.rejectedBeforeMultiLine) | ||
} | ||
if (typeof statementRaws.after !== `string`) {return} | ||
/** | ||
* @param {string} message | ||
*/ | ||
function complain (message) { | ||
if (context.fix) { | ||
const statementRaws = statement.raws | ||
if (primary.startsWith(`always`)) { | ||
const firstWhitespaceIndex = statementRaws.after.search(/\s/) | ||
const newlineBefore = firstWhitespaceIndex >= 0 ? statementRaws.after.slice(0, firstWhitespaceIndex) : statementRaws.after | ||
const newlineAfter = firstWhitespaceIndex >= 0 ? statementRaws.after.slice(firstWhitespaceIndex) : `` | ||
const newlineIndex = newlineAfter.search(/\r?\n/) | ||
if (typeof statementRaws.after !== `string`) { return } | ||
statementRaws.after = newlineIndex >= 0 ? newlineBefore + newlineAfter.slice(newlineIndex) : newlineBefore + context.newline + newlineAfter | ||
if (primary.startsWith(`always`)) { | ||
const firstWhitespaceIndex = statementRaws.after.search(/\s/) | ||
const newlineBefore = firstWhitespaceIndex >= 0 ? statementRaws.after.slice(0, firstWhitespaceIndex) : statementRaws.after | ||
const newlineAfter = firstWhitespaceIndex >= 0 ? statementRaws.after.slice(firstWhitespaceIndex) : `` | ||
const newlineIndex = newlineAfter.search(/\r?\n/) | ||
return | ||
} | ||
statementRaws.after = newlineIndex >= 0 ? newlineBefore + newlineAfter.slice(newlineIndex) : newlineBefore + context.newline + newlineAfter | ||
if (primary === `never-multi-line`) { | ||
statementRaws.after = statementRaws.after.replace(/\s/g, ``) | ||
return | ||
} | ||
return | ||
if (primary === `never-multi-line`) { | ||
statementRaws.after = statementRaws.after.replace(/\s/g, ``) | ||
return | ||
} | ||
} | ||
report({ | ||
message, | ||
result, | ||
ruleName, | ||
node: statement, | ||
index, | ||
}) | ||
} | ||
report({ | ||
message, | ||
result, | ||
ruleName, | ||
node: statement, | ||
index, | ||
}) | ||
} | ||
@@ -126,2 +128,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import blockString from "../../utils/blockString.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import hasBlock from "../../utils/hasBlock.js" | ||
import rawNodeString from "../../utils/rawNodeString.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -30,3 +30,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary) => { | ||
function rule (primary) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -99,2 +99,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import blockString from "../../utils/blockString.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import hasBlock from "../../utils/hasBlock.js" | ||
import hasEmptyBlock from "../../utils/hasEmptyBlock.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -31,3 +31,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -81,3 +81,3 @@ | ||
if (typeof statementRaws.after !== `string`) {return} | ||
if (typeof statementRaws.after !== `string`) { return } | ||
@@ -113,2 +113,3 @@ if (primary.startsWith(`always`)) { | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import beforeBlockString from "../../utils/beforeBlockString.js" | ||
import blockString from "../../utils/blockString.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import hasBlock from "../../utils/hasBlock.js" | ||
@@ -10,4 +12,2 @@ import hasEmptyBlock from "../../utils/hasEmptyBlock.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -32,3 +32,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions, context) => { | ||
function rule (primary, secondaryOptions, context) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -73,3 +73,3 @@ | ||
const backupCommentNextBefores = new Map() | ||
const backupCommentNextBefores = (new Map) | ||
@@ -83,3 +83,3 @@ /** | ||
function nextNode (startNode) { | ||
if (!startNode || !startNode.next) {return} | ||
if (!startNode || !startNode.next) { return } | ||
@@ -118,3 +118,3 @@ if (startNode.type === `comment`) { | ||
if (typeof nodeToCheckRaws.before !== `string`) {return} | ||
if (typeof nodeToCheckRaws.before !== `string`) { return } | ||
@@ -146,3 +146,3 @@ if (primary.startsWith(`always`)) { | ||
if (typeof fixTargetRaws.before !== `string`) {continue} | ||
if (typeof fixTargetRaws.before !== `string`) { continue } | ||
@@ -187,2 +187,3 @@ if (reNewLine.test(fixTargetRaws.before || ``)) { | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import beforeBlockString from "../../utils/beforeBlockString.js" | ||
import blockString from "../../utils/blockString.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import hasBlock from "../../utils/hasBlock.js" | ||
import hasEmptyBlock from "../../utils/hasEmptyBlock.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -31,3 +31,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -83,3 +83,3 @@ | ||
if (typeof statementRaws.between !== `string`) {return} | ||
if (typeof statementRaws.between !== `string`) { return } | ||
@@ -121,2 +121,3 @@ if (primary.startsWith(`always`)) { | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import beforeBlockString from "../../utils/beforeBlockString.js" | ||
import blockString from "../../utils/blockString.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import hasBlock from "../../utils/hasBlock.js" | ||
@@ -9,4 +11,2 @@ import hasEmptyBlock from "../../utils/hasEmptyBlock.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -34,3 +34,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions, context) => { | ||
function rule (primary, secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -89,3 +89,3 @@ | ||
if (statementFirst === null) {return} | ||
if (statementFirst === null) { return } | ||
@@ -121,2 +121,3 @@ if (primary.startsWith(`always`)) { | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import beforeBlockString from "../../utils/beforeBlockString.js" | ||
import blockString from "../../utils/blockString.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import hasBlock from "../../utils/hasBlock.js" | ||
@@ -10,4 +12,2 @@ import hasEmptyBlock from "../../utils/hasEmptyBlock.js" | ||
import { isRegExp, isString } from "../../utils/validateTypes.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -35,3 +35,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions, context) => { | ||
function rule (primary, secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -82,5 +82,3 @@ | ||
// Return early if at-rule is to be ignored | ||
if ( | ||
statement.type === `atrule` && optionsMatches(secondaryOptions, `ignoreAtRules`, statement.name) | ||
) { | ||
if (statement.type === `atrule` && optionsMatches(secondaryOptions, `ignoreAtRules`, statement.name)) { | ||
return | ||
@@ -90,5 +88,3 @@ } | ||
// Return early if selector is to be ignored | ||
if ( | ||
statement.type === `rule` && optionsMatches(secondaryOptions, `ignoreSelectors`, statement.selector) | ||
) { | ||
if (statement.type === `rule` && optionsMatches(secondaryOptions, `ignoreSelectors`, statement.selector)) { | ||
return | ||
@@ -143,2 +139,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import valueParser from "postcss-value-parser" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import getDeclarationValue from "../../utils/getDeclarationValue.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import setDeclarationValue from "../../utils/setDeclarationValue.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -30,49 +30,51 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
if (!validOptions) { | ||
return | ||
} | ||
root.walkDecls((decl) => { | ||
if (!CONTAINS_HEX.test(decl.value)) {return} | ||
root.walkDecls((decl) => { | ||
if (!CONTAINS_HEX.test(decl.value)) { return } | ||
const parsedValue = valueParser(getDeclarationValue(decl)) | ||
let needsFix = false | ||
const parsedValue = valueParser(getDeclarationValue(decl)) | ||
let needsFix = false | ||
parsedValue.walk((node) => { | ||
const { value } = node | ||
parsedValue.walk((node) => { | ||
const { value } = node | ||
if (isIgnoredFunction(node)) {return false} | ||
if (isIgnoredFunction(node)) { return false } | ||
if (!isHexColor(node)) {return} | ||
if (!isHexColor(node)) { return } | ||
const expected = primary === `lower` ? value.toLowerCase() : value.toUpperCase() | ||
const expected = primary === `lower` ? value.toLowerCase() : value.toUpperCase() | ||
if (value === expected) {return} | ||
if (value === expected) { return } | ||
if (context.fix) { | ||
node.value = expected | ||
needsFix = true | ||
if (context.fix) { | ||
node.value = expected | ||
needsFix = true | ||
return | ||
} | ||
return | ||
} | ||
report({ | ||
message: messages.expected(value, expected), | ||
node: decl, | ||
index: declarationValueIndex(decl) + node.sourceIndex, | ||
result, | ||
ruleName, | ||
report({ | ||
message: messages.expected(value, expected), | ||
node: decl, | ||
index: declarationValueIndex(decl) + node.sourceIndex, | ||
result, | ||
ruleName, | ||
}) | ||
}) | ||
if (needsFix) { | ||
setDeclarationValue(decl, parsedValue.toString()) | ||
} | ||
}) | ||
if (needsFix) { | ||
setDeclarationValue(decl, parsedValue.toString()) | ||
} | ||
}) | ||
} | ||
} | ||
@@ -97,2 +99,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import declarationBangSpaceChecker from "../../utils/declarationBangSpaceChecker.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import getDeclarationValue from "../../utils/getDeclarationValue.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import setDeclarationValue from "../../utils/setDeclarationValue.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -28,3 +28,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -47,41 +47,44 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (decl, index) => { | ||
let bangIndex = index - declarationValueIndex(decl) | ||
const declValue = getDeclarationValue(decl) | ||
let target | ||
/** @type {(value: string) => void} */ | ||
let setFixed | ||
fix: context.fix | ||
? (decl, index) => { | ||
let bangIndex = index - declarationValueIndex(decl) | ||
const declValue = getDeclarationValue(decl) | ||
let target | ||
if (bangIndex < declValue.length) { | ||
target = declValue | ||
setFixed = (value) => { | ||
setDeclarationValue(decl, value) | ||
/** @type {(value: string) => void} */ | ||
let setFixed | ||
if (bangIndex < declValue.length) { | ||
target = declValue | ||
setFixed = (value) => { | ||
setDeclarationValue(decl, value) | ||
} | ||
} else if (decl.important) { | ||
target = decl.raws.important || ` !important` | ||
bangIndex -= declValue.length | ||
setFixed = (value) => { | ||
decl.raws.important = value | ||
} | ||
} else { | ||
return false // not standard | ||
} | ||
} else if (decl.important) { | ||
target = decl.raws.important || ` !important` | ||
bangIndex -= declValue.length | ||
setFixed = (value) => { | ||
decl.raws.important = value | ||
} | ||
} else { | ||
return false // not standard | ||
} | ||
const targetBefore = target.slice(0, bangIndex + 1) | ||
const targetAfter = target.slice(bangIndex + 1) | ||
const targetBefore = target.slice(0, bangIndex + 1) | ||
const targetAfter = target.slice(bangIndex + 1) | ||
if (primary === `always`) { | ||
setFixed(targetBefore + targetAfter.replace(/^\s*/, ` `)) | ||
if (primary === `always`) { | ||
setFixed(targetBefore + targetAfter.replace(/^\s*/, ` `)) | ||
return true | ||
} | ||
return true | ||
} | ||
if (primary === `never`) { | ||
setFixed(targetBefore + targetAfter.replace(/^\s*/, ``)) | ||
if (primary === `never`) { | ||
setFixed(targetBefore + targetAfter.replace(/^\s*/, ``)) | ||
return true | ||
return true | ||
} | ||
return false | ||
} | ||
return false | ||
} : null, | ||
: null, | ||
}) | ||
@@ -94,2 +97,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import declarationBangSpaceChecker from "../../utils/declarationBangSpaceChecker.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import getDeclarationValue from "../../utils/getDeclarationValue.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import setDeclarationValue from "../../utils/setDeclarationValue.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -28,3 +28,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -47,41 +47,44 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (decl, index) => { | ||
let bangIndex = index - declarationValueIndex(decl) | ||
const value = getDeclarationValue(decl) | ||
let target | ||
/** @type {(val: string) => void} */ | ||
let setFixed | ||
fix: context.fix | ||
? (decl, index) => { | ||
let bangIndex = index - declarationValueIndex(decl) | ||
const value = getDeclarationValue(decl) | ||
let target | ||
if (bangIndex < value.length) { | ||
target = value | ||
setFixed = (val) => { | ||
setDeclarationValue(decl, val) | ||
/** @type {(val: string) => void} */ | ||
let setFixed | ||
if (bangIndex < value.length) { | ||
target = value | ||
setFixed = (val) => { | ||
setDeclarationValue(decl, val) | ||
} | ||
} else if (decl.important) { | ||
target = decl.raws.important || ` !important` | ||
bangIndex -= value.length | ||
setFixed = (val) => { | ||
decl.raws.important = val | ||
} | ||
} else { | ||
return false // not standard | ||
} | ||
} else if (decl.important) { | ||
target = decl.raws.important || ` !important` | ||
bangIndex -= value.length | ||
setFixed = (val) => { | ||
decl.raws.important = val | ||
} | ||
} else { | ||
return false // not standard | ||
} | ||
const targetBefore = target.slice(0, bangIndex) | ||
const targetAfter = target.slice(bangIndex) | ||
const targetBefore = target.slice(0, bangIndex) | ||
const targetAfter = target.slice(bangIndex) | ||
if (primary === `always`) { | ||
setFixed(`${targetBefore.replace(/\s*$/, ``)} ${targetAfter}`) | ||
if (primary === `always`) { | ||
setFixed(`${targetBefore.replace(/\s*$/, ``)} ${targetAfter}`) | ||
return true | ||
} | ||
return true | ||
} | ||
if (primary === `never`) { | ||
setFixed(targetBefore.replace(/\s*$/, ``) + targetAfter) | ||
if (primary === `never`) { | ||
setFixed(targetBefore.replace(/\s*$/, ``) + targetAfter) | ||
return true | ||
return true | ||
} | ||
return false | ||
} | ||
return false | ||
} : null, | ||
: null, | ||
}) | ||
@@ -94,2 +97,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import blockString from "../../utils/blockString.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import nextNonCommentNode from "../../utils/nextNonCommentNode.js" | ||
@@ -8,4 +10,2 @@ import rawNodeString from "../../utils/rawNodeString.js" | ||
import { isAtRule, isRule } from "../../utils/typeGuards.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -30,3 +30,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -48,3 +48,3 @@ | ||
if (!parentRule) {throw new Error(`A parent node must be present`)} | ||
if (!parentRule) { throw new Error(`A parent node must be present`) } | ||
@@ -109,2 +109,3 @@ if (!isAtRule(parentRule) && !isRule(parentRule)) { | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import blockString from "../../utils/blockString.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { isAtRule, isRule } from "../../utils/typeGuards.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -18,4 +18,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
expectedBeforeMultiLine: () => `Expected newline before ";" in a multi-line declaration block`, | ||
rejectedBeforeMultiLine: () => | ||
`Unexpected whitespace before ";" in a multi-line declaration block`, | ||
rejectedBeforeMultiLine: () => `Unexpected whitespace before ";" in a multi-line declaration block`, | ||
}) | ||
@@ -28,3 +27,3 @@ | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary) => { | ||
function rule (primary) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -45,3 +44,3 @@ | ||
if (!parentRule) {throw new Error(`A parent node must be present`)} | ||
if (!parentRule) { throw new Error(`A parent node must be present`) } | ||
@@ -79,2 +78,3 @@ if (!isAtRule(parentRule) && !isRule(parentRule)) { | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import blockString from "../../utils/blockString.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import rawNodeString from "../../utils/rawNodeString.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { isAtRule, isRule } from "../../utils/typeGuards.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -19,6 +19,4 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
rejectedAfter: () => `Unexpected whitespace after ";"`, | ||
expectedAfterSingleLine: () => | ||
`Expected single space after ";" in a single-line declaration block`, | ||
rejectedAfterSingleLine: () => | ||
`Unexpected whitespace after ";" in a single-line declaration block`, | ||
expectedAfterSingleLine: () => `Expected single space after ";" in a single-line declaration block`, | ||
rejectedAfterSingleLine: () => `Unexpected whitespace after ";" in a single-line declaration block`, | ||
}) | ||
@@ -32,3 +30,3 @@ | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -50,3 +48,3 @@ | ||
if (!parentRule) {throw new Error(`A parent node must be present`)} | ||
if (!parentRule) { throw new Error(`A parent node must be present`) } | ||
@@ -102,2 +100,3 @@ if (!isAtRule(parentRule) && !isRule(parentRule)) { | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import blockString from "../../utils/blockString.js" | ||
import getDeclarationValue from "../../utils/getDeclarationValue.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import setDeclarationValue from "../../utils/setDeclarationValue.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { isAtRule, isRule } from "../../utils/typeGuards.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -20,6 +20,4 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
rejectedBefore: () => `Unexpected whitespace before ";"`, | ||
expectedBeforeSingleLine: () => | ||
`Expected single space before ";" in a single-line declaration block`, | ||
rejectedBeforeSingleLine: () => | ||
`Unexpected whitespace before ";" in a single-line declaration block`, | ||
expectedBeforeSingleLine: () => `Expected single space before ";" in a single-line declaration block`, | ||
rejectedBeforeSingleLine: () => `Unexpected whitespace before ";" in a single-line declaration block`, | ||
}) | ||
@@ -33,3 +31,3 @@ | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -51,3 +49,3 @@ | ||
if (!parentRule) {throw new Error(`A parent node must be present`)} | ||
if (!parentRule) { throw new Error(`A parent node must be present`) } | ||
@@ -109,2 +107,3 @@ if (!isAtRule(parentRule) && !isRule(parentRule)) { | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import hasBlock from "../../utils/hasBlock.js" | ||
import { isAtRule } from "../../utils/typeGuards.js" | ||
import optionsMatches from "../../utils/optionsMatches.js" | ||
import { isAtRule } from "../../utils/typeGuards.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -26,116 +26,118 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}, | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
ignore: [`single-declaration`], | ||
function rule (primary, secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}, | ||
optional: true, | ||
}, | ||
) | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
ignore: [`single-declaration`], | ||
}, | ||
optional: true, | ||
}, | ||
) | ||
if (!validOptions) { | ||
return | ||
} | ||
root.walkAtRules((atRule) => { | ||
if (!atRule.parent) {throw new Error(`A parent node must be present`)} | ||
if (atRule.parent === root) { | ||
if (!validOptions) { | ||
return | ||
} | ||
if (atRule !== atRule.parent.last) { | ||
return | ||
} | ||
root.walkAtRules((atRule) => { | ||
if (!atRule.parent) { throw new Error(`A parent node must be present`) } | ||
if (hasBlock(atRule)) { | ||
return | ||
} | ||
if (atRule.parent === root) { | ||
return | ||
} | ||
checkLastNode(atRule) | ||
}) | ||
if (atRule !== atRule.parent.last) { | ||
return | ||
} | ||
root.walkDecls((decl) => { | ||
if (!decl.parent) {throw new Error(`A parent node must be present`)} | ||
if (hasBlock(atRule)) { | ||
return | ||
} | ||
if (decl.parent.type === `object`) { | ||
return | ||
} | ||
checkLastNode(atRule) | ||
}) | ||
if (decl !== decl.parent.last) { | ||
return | ||
} | ||
root.walkDecls((decl) => { | ||
if (!decl.parent) { throw new Error(`A parent node must be present`) } | ||
checkLastNode(decl) | ||
}) | ||
if (decl.parent.type === `object`) { | ||
return | ||
} | ||
/** | ||
* @param {import('postcss').Node} node | ||
*/ | ||
function checkLastNode (node) { | ||
if (!node.parent) {throw new Error(`A parent node must be present`)} | ||
if (decl !== decl.parent.last) { | ||
return | ||
} | ||
const hasSemicolon = node.parent.raws.semicolon | ||
const ignoreSingleDeclaration = optionsMatches( | ||
secondaryOptions, | ||
`ignore`, | ||
`single-declaration`, | ||
) | ||
checkLastNode(decl) | ||
}) | ||
if (ignoreSingleDeclaration && node.parent.first === node) { | ||
return | ||
} | ||
/** | ||
* @param {import('postcss').Node} node | ||
*/ | ||
function checkLastNode (node) { | ||
if (!node.parent) { throw new Error(`A parent node must be present`) } | ||
let message | ||
const hasSemicolon = node.parent.raws.semicolon | ||
const ignoreSingleDeclaration = optionsMatches( | ||
secondaryOptions, | ||
`ignore`, | ||
`single-declaration`, | ||
) | ||
if (primary === `always`) { | ||
if (hasSemicolon) { | ||
if (ignoreSingleDeclaration && node.parent.first === node) { | ||
return | ||
} | ||
// auto-fix | ||
if (context.fix) { | ||
node.parent.raws.semicolon = true | ||
let message | ||
if (isAtRule(node)) { | ||
node.raws.between = `` | ||
node.parent.raws.after = ` ` | ||
if (primary === `always`) { | ||
if (hasSemicolon) { | ||
return | ||
} | ||
return | ||
} | ||
// auto-fix | ||
if (context.fix) { | ||
node.parent.raws.semicolon = true | ||
message = messages.expected | ||
} else if (primary === `never`) { | ||
if (!hasSemicolon) { | ||
return | ||
} | ||
if (isAtRule(node)) { | ||
node.raws.between = `` | ||
node.parent.raws.after = ` ` | ||
} | ||
// auto-fix | ||
if (context.fix) { | ||
node.parent.raws.semicolon = false | ||
return | ||
} | ||
return | ||
message = messages.expected | ||
} else if (primary === `never`) { | ||
if (!hasSemicolon) { | ||
return | ||
} | ||
// auto-fix | ||
if (context.fix) { | ||
node.parent.raws.semicolon = false | ||
return | ||
} | ||
message = messages.rejected | ||
} else { | ||
throw new Error(`Unexpected primary option: "${primary}"`) | ||
} | ||
message = messages.rejected | ||
} else { | ||
throw new Error(`Unexpected primary option: "${primary}"`) | ||
report({ | ||
message, | ||
node, | ||
index: node.toString().trim().length - 1, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
report({ | ||
message, | ||
node, | ||
index: node.toString().trim().length - 1, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
@@ -147,2 +149,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isStandardSyntaxDeclaration from "../../utils/isStandardSyntaxDeclaration.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -26,3 +26,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -57,3 +57,3 @@ | ||
const indexToCheck = /^[^\S\r\n]*\/\*/.test(propPlusColon.slice(i + 1)) ? propPlusColon.indexOf(`*/`, i) + 1 : i | ||
const indexToCheck = (/^[^\S\r\n]*\/\*/).test(propPlusColon.slice(i + 1)) ? propPlusColon.indexOf(`*/`, i) + 1 : i | ||
@@ -68,3 +68,3 @@ checker.afterOneOnly({ | ||
if (between === null) {throw new Error(`\`between\` must be present`)} | ||
if (between === null) { throw new Error(`\`between\` must be present`) } | ||
@@ -76,3 +76,3 @@ const betweenStart = declarationValueIndex(decl) - between.length | ||
decl.raws.between = /^\s*\n/.test(betweenAfter) ? betweenBefore + betweenAfter.replace(/^[^\S\r\n]*/, ``) : betweenBefore + context.newline + betweenAfter | ||
decl.raws.between = (/^\s*\n/).test(betweenAfter) ? betweenBefore + betweenAfter.replace(/^[^\S\r\n]*/, ``) : betweenBefore + context.newline + betweenAfter | ||
@@ -99,2 +99,3 @@ return | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import declarationColonSpaceChecker from "../../utils/declarationColonSpaceChecker.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,3 +27,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -46,22 +46,24 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (decl, index) => { | ||
const colonIndex = index - declarationValueIndex(decl) | ||
const between = decl.raws.between | ||
fix: context.fix | ||
? (decl, index) => { | ||
const colonIndex = index - declarationValueIndex(decl) | ||
const between = decl.raws.between | ||
if (between === null) {throw new Error(`\`between\` must be present`)} | ||
if (between === null) { throw new Error(`\`between\` must be present`) } | ||
if (primary.startsWith(`always`)) { | ||
decl.raws.between = between.slice(0, colonIndex) + between.slice(colonIndex).replace(/^:\s*/, `: `) | ||
if (primary.startsWith(`always`)) { | ||
decl.raws.between = between.slice(0, colonIndex) + between.slice(colonIndex).replace(/^:\s*/, `: `) | ||
return true | ||
} | ||
return true | ||
} | ||
if (primary === `never`) { | ||
decl.raws.between = between.slice(0, colonIndex) + between.slice(colonIndex).replace(/^:\s*/, `:`) | ||
if (primary === `never`) { | ||
decl.raws.between = between.slice(0, colonIndex) + between.slice(colonIndex).replace(/^:\s*/, `:`) | ||
return true | ||
return true | ||
} | ||
return false | ||
} | ||
return false | ||
} : null, | ||
: null, | ||
}) | ||
@@ -74,2 +76,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import declarationColonSpaceChecker from "../../utils/declarationColonSpaceChecker.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -26,3 +26,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -45,22 +45,24 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (decl, index) => { | ||
const colonIndex = index - declarationValueIndex(decl) | ||
const between = decl.raws.between | ||
fix: context.fix | ||
? (decl, index) => { | ||
const colonIndex = index - declarationValueIndex(decl) | ||
const between = decl.raws.between | ||
if (between === null) {throw new Error(`\`between\` must be present`)} | ||
if (between === null) { throw new Error(`\`between\` must be present`) } | ||
if (primary === `always`) { | ||
decl.raws.between = between.slice(0, colonIndex).replace(/\s*$/, ` `) + between.slice(colonIndex) | ||
if (primary === `always`) { | ||
decl.raws.between = between.slice(0, colonIndex).replace(/\s*$/, ` `) + between.slice(colonIndex) | ||
return true | ||
} | ||
return true | ||
} | ||
if (primary === `never`) { | ||
decl.raws.between = between.slice(0, colonIndex).replace(/\s*$/, ``) + between.slice(colonIndex) | ||
if (primary === `never`) { | ||
decl.raws.between = between.slice(0, colonIndex).replace(/\s*$/, ``) + between.slice(colonIndex) | ||
return true | ||
return true | ||
} | ||
return false | ||
} | ||
return false | ||
} : null, | ||
: null, | ||
}) | ||
@@ -73,2 +75,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import fixer from "../../utils/functionCommaSpaceFix.js" | ||
import functionCommaSpaceChecker from "../../utils/functionCommaSpaceChecker.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,3 +27,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -46,4 +46,4 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (div, index, nodes) => | ||
fixer({ | ||
fix: context.fix | ||
? (div, index, nodes) => fixer({ | ||
div, | ||
@@ -55,3 +55,4 @@ index, | ||
symb: context.newline || ``, | ||
}) : null, | ||
}) | ||
: null, | ||
}) | ||
@@ -64,2 +65,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import fixer from "../../utils/functionCommaSpaceFix.js" | ||
import functionCommaSpaceChecker from "../../utils/functionCommaSpaceChecker.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,3 +27,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -46,4 +46,4 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (div, index, nodes) => | ||
fixer({ | ||
fix: context.fix | ||
? (div, index, nodes) => fixer({ | ||
div, | ||
@@ -55,3 +55,4 @@ index, | ||
symb: context.newline || ``, | ||
}) : null, | ||
}) | ||
: null, | ||
}) | ||
@@ -64,2 +65,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import fixer from "../../utils/functionCommaSpaceFix.js" | ||
import functionCommaSpaceChecker from "../../utils/functionCommaSpaceChecker.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -28,3 +28,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -47,4 +47,4 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (div, index, nodes) => | ||
fixer({ | ||
fix: context.fix | ||
? (div, index, nodes) => fixer({ | ||
div, | ||
@@ -56,3 +56,4 @@ index, | ||
symb: ` `, | ||
}) : null, | ||
}) | ||
: null, | ||
}) | ||
@@ -65,2 +66,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import fixer from "../../utils/functionCommaSpaceFix.js" | ||
import functionCommaSpaceChecker from "../../utils/functionCommaSpaceChecker.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -28,3 +28,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -47,4 +47,4 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (div, index, nodes) => | ||
fixer({ | ||
fix: context.fix | ||
? (div, index, nodes) => fixer({ | ||
div, | ||
@@ -56,3 +56,4 @@ index, | ||
symb: ` `, | ||
}) : null, | ||
}) | ||
: null, | ||
}) | ||
@@ -65,2 +66,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import valueParser from "postcss-value-parser" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import getDeclarationValue from "../../utils/getDeclarationValue.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import { isNumber } from "../../utils/validateTypes.js" | ||
import setDeclarationValue from "../../utils/setDeclarationValue.js" | ||
import { isNumber } from "../../utils/validateTypes.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -29,3 +29,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
function placeIndexOnValueStart (decl) { | ||
if (decl.raws.between === null) {throw new Error(`\`between\` must be present`)} | ||
if (decl.raws.between === null) { throw new Error(`\`between\` must be present`) } | ||
@@ -36,3 +36,3 @@ return decl.prop.length + decl.raws.between.length - 1 | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const maxAdjacentNewlines = primary + 1 | ||
@@ -61,2 +61,3 @@ | ||
const stringValue = getDeclarationValue(decl) | ||
/** @type {Array<[string, string]>} */ | ||
@@ -110,2 +111,3 @@ const splittedValue = [] | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import valueParser from "postcss-value-parser" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import getDeclarationValue from "../../utils/getDeclarationValue.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isSingleLineString from "../../utils/isSingleLineString.js" | ||
import isStandardSyntaxFunction from "../../utils/isStandardSyntaxFunction.js" | ||
import setDeclarationValue from "../../utils/setDeclarationValue.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -33,117 +33,120 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `always-multi-line`, `never-multi-line`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `always-multi-line`, `never-multi-line`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
root.walkDecls((decl) => { | ||
if (!decl.value.includes(`(`)) { | ||
if (!validOptions) { | ||
return | ||
} | ||
let hasFixed = false | ||
const declValue = getDeclarationValue(decl) | ||
const parsedValue = valueParser(declValue) | ||
parsedValue.walk((valueNode) => { | ||
if (valueNode.type !== `function`) { | ||
root.walkDecls((decl) => { | ||
if (!decl.value.includes(`(`)) { | ||
return | ||
} | ||
if (!isStandardSyntaxFunction(valueNode)) { | ||
return | ||
} | ||
let hasFixed = false | ||
const declValue = getDeclarationValue(decl) | ||
const parsedValue = valueParser(declValue) | ||
const functionString = valueParser.stringify(valueNode) | ||
const isMultiLine = !isSingleLineString(functionString) | ||
const containsNewline = (/** @type {string} */ str) => str.includes(`\n`) | ||
parsedValue.walk((valueNode) => { | ||
if (valueNode.type !== `function`) { | ||
return | ||
} | ||
// Check opening ... | ||
if (!isStandardSyntaxFunction(valueNode)) { | ||
return | ||
} | ||
const openingIndex = valueNode.sourceIndex + valueNode.value.length + 1 | ||
const checkBefore = getCheckBefore(valueNode) | ||
const functionString = valueParser.stringify(valueNode) | ||
const isMultiLine = !isSingleLineString(functionString) | ||
if (primary === `always` && !containsNewline(checkBefore)) { | ||
if (context.fix) { | ||
hasFixed = true | ||
fixBeforeForAlways(valueNode, context.newline || ``) | ||
} else { | ||
complain(messages.expectedOpening, openingIndex) | ||
function containsNewline (/** @type {string} */ str) { | ||
return str.includes(`\n`) | ||
} | ||
} | ||
if (isMultiLine && primary === `always-multi-line` && !containsNewline(checkBefore)) { | ||
if (context.fix) { | ||
hasFixed = true | ||
fixBeforeForAlways(valueNode, context.newline || ``) | ||
} else { | ||
complain(messages.expectedOpeningMultiLine, openingIndex) | ||
// Check opening ... | ||
const openingIndex = valueNode.sourceIndex + valueNode.value.length + 1 | ||
const checkBefore = getCheckBefore(valueNode) | ||
if (primary === `always` && !containsNewline(checkBefore)) { | ||
if (context.fix) { | ||
hasFixed = true | ||
fixBeforeForAlways(valueNode, context.newline || ``) | ||
} else { | ||
complain(messages.expectedOpening, openingIndex) | ||
} | ||
} | ||
} | ||
if (isMultiLine && primary === `never-multi-line` && checkBefore !== ``) { | ||
if (context.fix) { | ||
hasFixed = true | ||
fixBeforeForNever(valueNode) | ||
} else { | ||
complain(messages.rejectedOpeningMultiLine, openingIndex) | ||
if (isMultiLine && primary === `always-multi-line` && !containsNewline(checkBefore)) { | ||
if (context.fix) { | ||
hasFixed = true | ||
fixBeforeForAlways(valueNode, context.newline || ``) | ||
} else { | ||
complain(messages.expectedOpeningMultiLine, openingIndex) | ||
} | ||
} | ||
} | ||
// Check closing ... | ||
if (isMultiLine && primary === `never-multi-line` && checkBefore !== ``) { | ||
if (context.fix) { | ||
hasFixed = true | ||
fixBeforeForNever(valueNode) | ||
} else { | ||
complain(messages.rejectedOpeningMultiLine, openingIndex) | ||
} | ||
} | ||
const closingIndex = valueNode.sourceIndex + functionString.length - 2 | ||
const checkAfter = getCheckAfter(valueNode) | ||
// Check closing ... | ||
const closingIndex = valueNode.sourceIndex + functionString.length - 2 | ||
const checkAfter = getCheckAfter(valueNode) | ||
if (primary === `always` && !containsNewline(checkAfter)) { | ||
if (context.fix) { | ||
hasFixed = true | ||
fixAfterForAlways(valueNode, context.newline || ``) | ||
} else { | ||
complain(messages.expectedClosing, closingIndex) | ||
if (primary === `always` && !containsNewline(checkAfter)) { | ||
if (context.fix) { | ||
hasFixed = true | ||
fixAfterForAlways(valueNode, context.newline || ``) | ||
} else { | ||
complain(messages.expectedClosing, closingIndex) | ||
} | ||
} | ||
} | ||
if (isMultiLine && primary === `always-multi-line` && !containsNewline(checkAfter)) { | ||
if (context.fix) { | ||
hasFixed = true | ||
fixAfterForAlways(valueNode, context.newline || ``) | ||
} else { | ||
complain(messages.expectedClosingMultiLine, closingIndex) | ||
if (isMultiLine && primary === `always-multi-line` && !containsNewline(checkAfter)) { | ||
if (context.fix) { | ||
hasFixed = true | ||
fixAfterForAlways(valueNode, context.newline || ``) | ||
} else { | ||
complain(messages.expectedClosingMultiLine, closingIndex) | ||
} | ||
} | ||
} | ||
if (isMultiLine && primary === `never-multi-line` && checkAfter !== ``) { | ||
if (context.fix) { | ||
hasFixed = true | ||
fixAfterForNever(valueNode) | ||
} else { | ||
complain(messages.rejectedClosingMultiLine, closingIndex) | ||
if (isMultiLine && primary === `never-multi-line` && checkAfter !== ``) { | ||
if (context.fix) { | ||
hasFixed = true | ||
fixAfterForNever(valueNode) | ||
} else { | ||
complain(messages.rejectedClosingMultiLine, closingIndex) | ||
} | ||
} | ||
}) | ||
if (hasFixed) { | ||
setDeclarationValue(decl, parsedValue.toString()) | ||
} | ||
/** | ||
* @param {string} message | ||
* @param {number} offset | ||
*/ | ||
function complain (message, offset) { | ||
report({ | ||
ruleName, | ||
result, | ||
message, | ||
node: decl, | ||
index: declarationValueIndex(decl) + offset, | ||
}) | ||
} | ||
}) | ||
if (hasFixed) { | ||
setDeclarationValue(decl, parsedValue.toString()) | ||
} | ||
/** | ||
* @param {string} message | ||
* @param {number} offset | ||
*/ | ||
function complain (message, offset) { | ||
report({ | ||
ruleName, | ||
result, | ||
message, | ||
node: decl, | ||
index: declarationValueIndex(decl) + offset, | ||
}) | ||
} | ||
}) | ||
} | ||
} | ||
@@ -277,2 +280,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import valueParser from "postcss-value-parser" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import getDeclarationValue from "../../utils/getDeclarationValue.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isSingleLineString from "../../utils/isSingleLineString.js" | ||
import isStandardSyntaxFunction from "../../utils/isStandardSyntaxFunction.js" | ||
import setDeclarationValue from "../../utils/setDeclarationValue.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -35,137 +35,137 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`, `always-single-line`, `never-single-line`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`, `always-single-line`, `never-single-line`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
root.walkDecls((decl) => { | ||
if (!decl.value.includes(`(`)) { | ||
if (!validOptions) { | ||
return | ||
} | ||
let hasFixed = false | ||
const declValue = getDeclarationValue(decl) | ||
const parsedValue = valueParser(declValue) | ||
parsedValue.walk((valueNode) => { | ||
if (valueNode.type !== `function`) { | ||
root.walkDecls((decl) => { | ||
if (!decl.value.includes(`(`)) { | ||
return | ||
} | ||
if (!isStandardSyntaxFunction(valueNode)) { | ||
return | ||
} | ||
let hasFixed = false | ||
const declValue = getDeclarationValue(decl) | ||
const parsedValue = valueParser(declValue) | ||
// Ignore function without parameters | ||
if (!valueNode.nodes.length) { | ||
return | ||
} | ||
parsedValue.walk((valueNode) => { | ||
if (valueNode.type !== `function`) { | ||
return | ||
} | ||
const functionString = valueParser.stringify(valueNode) | ||
const isSingleLine = isSingleLineString(functionString) | ||
if (!isStandardSyntaxFunction(valueNode)) { | ||
return | ||
} | ||
// Check opening ... | ||
// Ignore function without parameters | ||
if (!valueNode.nodes.length) { | ||
return | ||
} | ||
const openingIndex = valueNode.sourceIndex + valueNode.value.length + 1 | ||
const functionString = valueParser.stringify(valueNode) | ||
const isSingleLine = isSingleLineString(functionString) | ||
if (primary === `always` && valueNode.before !== ` `) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.before = ` ` | ||
} else { | ||
complain(messages.expectedOpening, openingIndex) | ||
// Check opening ... | ||
const openingIndex = valueNode.sourceIndex + valueNode.value.length + 1 | ||
if (primary === `always` && valueNode.before !== ` `) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.before = ` ` | ||
} else { | ||
complain(messages.expectedOpening, openingIndex) | ||
} | ||
} | ||
} | ||
if (primary === `never` && valueNode.before !== ``) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.before = `` | ||
} else { | ||
complain(messages.rejectedOpening, openingIndex) | ||
if (primary === `never` && valueNode.before !== ``) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.before = `` | ||
} else { | ||
complain(messages.rejectedOpening, openingIndex) | ||
} | ||
} | ||
} | ||
if (isSingleLine && primary === `always-single-line` && valueNode.before !== ` `) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.before = ` ` | ||
} else { | ||
complain(messages.expectedOpeningSingleLine, openingIndex) | ||
if (isSingleLine && primary === `always-single-line` && valueNode.before !== ` `) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.before = ` ` | ||
} else { | ||
complain(messages.expectedOpeningSingleLine, openingIndex) | ||
} | ||
} | ||
} | ||
if (isSingleLine && primary === `never-single-line` && valueNode.before !== ``) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.before = `` | ||
} else { | ||
complain(messages.rejectedOpeningSingleLine, openingIndex) | ||
if (isSingleLine && primary === `never-single-line` && valueNode.before !== ``) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.before = `` | ||
} else { | ||
complain(messages.rejectedOpeningSingleLine, openingIndex) | ||
} | ||
} | ||
} | ||
// Check closing ... | ||
// Check closing ... | ||
const closingIndex = valueNode.sourceIndex + functionString.length - 2 | ||
const closingIndex = valueNode.sourceIndex + functionString.length - 2 | ||
if (primary === `always` && valueNode.after !== ` `) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.after = ` ` | ||
} else { | ||
complain(messages.expectedClosing, closingIndex) | ||
} | ||
} | ||
if (primary === `always` && valueNode.after !== ` `) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.after = ` ` | ||
} else { | ||
complain(messages.expectedClosing, closingIndex) | ||
if (primary === `never` && valueNode.after !== ``) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.after = `` | ||
} else { | ||
complain(messages.rejectedClosing, closingIndex) | ||
} | ||
} | ||
} | ||
if (primary === `never` && valueNode.after !== ``) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.after = `` | ||
} else { | ||
complain(messages.rejectedClosing, closingIndex) | ||
if (isSingleLine && primary === `always-single-line` && valueNode.after !== ` `) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.after = ` ` | ||
} else { | ||
complain(messages.expectedClosingSingleLine, closingIndex) | ||
} | ||
} | ||
} | ||
if (isSingleLine && primary === `always-single-line` && valueNode.after !== ` `) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.after = ` ` | ||
} else { | ||
complain(messages.expectedClosingSingleLine, closingIndex) | ||
if (isSingleLine && primary === `never-single-line` && valueNode.after !== ``) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.after = `` | ||
} else { | ||
complain(messages.rejectedClosingSingleLine, closingIndex) | ||
} | ||
} | ||
}) | ||
if (hasFixed) { | ||
setDeclarationValue(decl, parsedValue.toString()) | ||
} | ||
if (isSingleLine && primary === `never-single-line` && valueNode.after !== ``) { | ||
if (context.fix) { | ||
hasFixed = true | ||
valueNode.after = `` | ||
} else { | ||
complain(messages.rejectedClosingSingleLine, closingIndex) | ||
} | ||
/** | ||
* @param {string} message | ||
* @param {number} offset | ||
*/ | ||
function complain (message, offset) { | ||
report({ | ||
ruleName, | ||
result, | ||
message, | ||
node: decl, | ||
index: declarationValueIndex(decl) + offset, | ||
}) | ||
} | ||
}) | ||
if (hasFixed) { | ||
setDeclarationValue(decl, parsedValue.toString()) | ||
} | ||
/** | ||
* @param {string} message | ||
* @param {number} offset | ||
*/ | ||
function complain (message, offset) { | ||
report({ | ||
ruleName, | ||
result, | ||
message, | ||
node: decl, | ||
index: declarationValueIndex(decl) + offset, | ||
}) | ||
} | ||
}) | ||
} | ||
} | ||
@@ -176,2 +176,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import styleSearch from "style-search" | ||
import styleSearch from "style-search" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import getDeclarationValue from "../../utils/getDeclarationValue.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isWhitespace from "../../utils/isWhitespace.js" | ||
import setDeclarationValue from "../../utils/setDeclarationValue.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -31,156 +31,159 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
if (!validOptions) { | ||
return | ||
} | ||
/** | ||
* @param {import('postcss').Node} node | ||
* @param {string} value | ||
* @param {number} nodeIndex | ||
* @param {((index: number) => void) | undefined} fix | ||
*/ | ||
function check (node, value, nodeIndex, fix) { | ||
styleSearch( | ||
{ | ||
source: value, | ||
target: `)`, | ||
functionArguments: `only`, | ||
}, | ||
(match) => { | ||
checkClosingParen(value, match.startIndex + 1, node, nodeIndex, fix) | ||
}, | ||
) | ||
} | ||
/** | ||
* @param {import('postcss').Node} node | ||
* @param {string} value | ||
* @param {number} nodeIndex | ||
* @param {((index: number) => void) | undefined} fix | ||
*/ | ||
function check (node, value, nodeIndex, fix) { | ||
styleSearch( | ||
{ | ||
source: value, | ||
target: `)`, | ||
functionArguments: `only`, | ||
}, | ||
(match) => { | ||
checkClosingParen(value, match.startIndex + 1, node, nodeIndex, fix) | ||
}, | ||
) | ||
} | ||
/** | ||
* @param {string} source | ||
* @param {number} index | ||
* @param {import('postcss').Node} node | ||
* @param {number} nodeIndex | ||
* @param {((index: number) => void) | undefined} fix | ||
*/ | ||
function checkClosingParen (source, index, node, nodeIndex, fix) { | ||
const nextChar = source.charAt(index) | ||
/** | ||
* @param {string} source | ||
* @param {number} index | ||
* @param {import('postcss').Node} node | ||
* @param {number} nodeIndex | ||
* @param {((index: number) => void) | undefined} fix | ||
*/ | ||
function checkClosingParen (source, index, node, nodeIndex, fix) { | ||
const nextChar = source.charAt(index) | ||
if (!nextChar) {return} | ||
if (!nextChar) { return } | ||
if (primary === `always`) { | ||
// Allow for the next character to be a single empty space, | ||
// another closing parenthesis, a comma, or the end of the value | ||
if (nextChar === ` `) { | ||
return | ||
} | ||
if (primary === `always`) { | ||
// Allow for the next character to be a single empty space, | ||
// another closing parenthesis, a comma, or the end of the value | ||
if (nextChar === ` `) { | ||
return | ||
} | ||
if (nextChar === `\n`) { | ||
return | ||
} | ||
if (nextChar === `\n`) { | ||
return | ||
} | ||
if (source.slice(index, index + 2) === `\r\n`) { | ||
return | ||
} | ||
if (source.slice(index, index + 2) === `\r\n`) { | ||
return | ||
} | ||
if (ACCEPTABLE_AFTER_CLOSING_PAREN.has(nextChar)) { | ||
return | ||
} | ||
if (ACCEPTABLE_AFTER_CLOSING_PAREN.has(nextChar)) { | ||
return | ||
} | ||
if (fix) { | ||
fix(index) | ||
if (fix) { | ||
fix(index) | ||
return | ||
} | ||
return | ||
} | ||
report({ | ||
message: messages.expected, | ||
node, | ||
index: nodeIndex + index, | ||
result, | ||
ruleName, | ||
}) | ||
} else if (primary === `never` && isWhitespace(nextChar)) { | ||
if (fix) { | ||
fix(index) | ||
report({ | ||
message: messages.expected, | ||
node, | ||
index: nodeIndex + index, | ||
result, | ||
ruleName, | ||
}) | ||
} else if (primary === `never` && isWhitespace(nextChar)) { | ||
if (fix) { | ||
fix(index) | ||
return | ||
return | ||
} | ||
report({ | ||
message: messages.rejected, | ||
node, | ||
index: nodeIndex + index, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
report({ | ||
message: messages.rejected, | ||
node, | ||
index: nodeIndex + index, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
} | ||
/** | ||
* @param {string} value | ||
*/ | ||
function createFixer (value) { | ||
let fixed = `` | ||
let lastIndex = 0 | ||
/** @type {(index: number) => void} */ | ||
let applyFix | ||
/** | ||
* @param {string} value | ||
*/ | ||
function createFixer (value) { | ||
let fixed = `` | ||
let lastIndex = 0 | ||
if (primary === `always`) { | ||
applyFix = (index) => { | ||
fixed += `${value.slice(lastIndex, index)} ` | ||
lastIndex = index | ||
} | ||
} else if (primary === `never`) { | ||
applyFix = (index) => { | ||
let whitespaceEndIndex = index + 1 | ||
/** @type {(index: number) => void} */ | ||
let applyFix | ||
while (whitespaceEndIndex < value.length && isWhitespace(value.charAt(whitespaceEndIndex))) { | ||
whitespaceEndIndex++ | ||
if (primary === `always`) { | ||
applyFix = (index) => { | ||
fixed += `${value.slice(lastIndex, index)} ` | ||
lastIndex = index | ||
} | ||
} else if (primary === `never`) { | ||
applyFix = (index) => { | ||
let whitespaceEndIndex = index + 1 | ||
fixed += value.slice(lastIndex, index) | ||
lastIndex = whitespaceEndIndex | ||
while (whitespaceEndIndex < value.length && isWhitespace(value.charAt(whitespaceEndIndex))) { | ||
whitespaceEndIndex++ | ||
} | ||
fixed += value.slice(lastIndex, index) | ||
lastIndex = whitespaceEndIndex | ||
} | ||
} else { | ||
throw new Error(`Unexpected option: "${primary}"`) | ||
} | ||
} else { | ||
throw new Error(`Unexpected option: "${primary}"`) | ||
} | ||
return { | ||
applyFix, | ||
get hasFixed () { | ||
return Boolean(lastIndex) | ||
}, | ||
get fixed () { | ||
return fixed + value.slice(lastIndex) | ||
}, | ||
return { | ||
applyFix, | ||
get hasFixed () { | ||
return Boolean(lastIndex) | ||
}, | ||
get fixed () { | ||
return fixed + value.slice(lastIndex) | ||
}, | ||
} | ||
} | ||
} | ||
root.walkAtRules(/^import$/i, (atRule) => { | ||
const param = (atRule.raws.params && atRule.raws.params.raw) || atRule.params | ||
const fixer = context.fix && createFixer(param) | ||
root.walkAtRules(/^import$/i, (atRule) => { | ||
const param = (atRule.raws.params && atRule.raws.params.raw) || atRule.params | ||
const fixer = context.fix && createFixer(param) | ||
check(atRule, param, atRuleParamIndex(atRule), fixer ? fixer.applyFix : undefined) | ||
check(atRule, param, atRuleParamIndex(atRule), fixer ? fixer.applyFix : undefined) | ||
if (fixer && fixer.hasFixed) { | ||
if (atRule.raws.params) { | ||
atRule.raws.params.raw = fixer.fixed | ||
} else { | ||
atRule.params = fixer.fixed | ||
if (fixer && fixer.hasFixed) { | ||
if (atRule.raws.params) { | ||
atRule.raws.params.raw = fixer.fixed | ||
} else { | ||
atRule.params = fixer.fixed | ||
} | ||
} | ||
} | ||
}) | ||
root.walkDecls((decl) => { | ||
const value = getDeclarationValue(decl) | ||
const fixer = context.fix && createFixer(value) | ||
}) | ||
root.walkDecls((decl) => { | ||
const value = getDeclarationValue(decl) | ||
const fixer = context.fix && createFixer(value) | ||
check(decl, value, declarationValueIndex(decl), fixer ? fixer.applyFix : undefined) | ||
check(decl, value, declarationValueIndex(decl), fixer ? fixer.applyFix : undefined) | ||
if (fixer && fixer.hasFixed) { | ||
setDeclarationValue(decl, fixer.fixed) | ||
} | ||
}) | ||
if (fixer && fixer.hasFixed) { | ||
setDeclarationValue(decl, fixer.fixed) | ||
} | ||
}) | ||
} | ||
} | ||
@@ -191,2 +194,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import styleSearch from "style-search" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import beforeBlockString from "../../utils/beforeBlockString.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import hasBlock from "../../utils/hasBlock.js" | ||
import optionsMatches from "../../utils/optionsMatches.js" | ||
import { assertString, isBoolean, isNumber, isString } from "../../utils/validateTypes.js" | ||
import { isAtRule, isDeclaration, isRoot, isRule } from "../../utils/typeGuards.js" | ||
import { isBoolean, isNumber, isString, assertString } from "../../utils/validateTypes.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -17,2 +17,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
export const ruleName = addNamespace(shortName) | ||
export const messages = ruleMessages(ruleName, { | ||
@@ -28,415 +29,417 @@ expected: (x) => `Expected indentation of ${x}`, | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions = {}, context) => (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
possible: [isNumber, `tab`], | ||
}, | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
baseIndentLevel: [isNumber, `auto`], | ||
except: [`block`, `value`, `param`], | ||
ignore: [`value`, `param`, `inside-parens`], | ||
indentInsideParens: [`twice`, `once-at-root-twice-in-block`], | ||
indentClosingBrace: [isBoolean], | ||
function rule (primary, secondaryOptions = {}, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
possible: [isNumber, `tab`], | ||
}, | ||
optional: true, | ||
}, | ||
) | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
baseIndentLevel: [isNumber, `auto`], | ||
except: [`block`, `value`, `param`], | ||
ignore: [`value`, `param`, `inside-parens`], | ||
indentInsideParens: [`twice`, `once-at-root-twice-in-block`], | ||
indentClosingBrace: [isBoolean], | ||
}, | ||
optional: true, | ||
}, | ||
) | ||
if (!validOptions) { | ||
return | ||
} | ||
if (!validOptions) { | ||
return | ||
} | ||
const spaceCount = isNumber(primary) ? primary : null | ||
const indentChar = spaceCount === null ? `\t` : ` `.repeat(spaceCount) | ||
const warningWord = primary === `tab` ? `tab` : `space` | ||
const spaceCount = isNumber(primary) ? primary : null | ||
const indentChar = spaceCount === null ? `\t` : ` `.repeat(spaceCount) | ||
const warningWord = primary === `tab` ? `tab` : `space` | ||
/** @type {number | 'auto'} */ | ||
const baseIndentLevel = secondaryOptions.baseIndentLevel | ||
/** @type {boolean} */ | ||
const indentClosingBrace = secondaryOptions.indentClosingBrace | ||
/** @type {number | 'auto'} */ | ||
const baseIndentLevel = secondaryOptions.baseIndentLevel | ||
/** | ||
* @param {number} level | ||
*/ | ||
const legibleExpectation = (level) => { | ||
const count = spaceCount === null ? level : level * spaceCount | ||
const quantifiedWarningWord = count === 1 ? warningWord : `${warningWord}s` | ||
/** @type {boolean} */ | ||
const indentClosingBrace = secondaryOptions.indentClosingBrace | ||
return `${count} ${quantifiedWarningWord}` | ||
} | ||
/** | ||
* @param {number} level | ||
*/ | ||
function legibleExpectation (level) { | ||
const count = spaceCount === null ? level : level * spaceCount | ||
const quantifiedWarningWord = count === 1 ? warningWord : `${warningWord}s` | ||
// Cycle through all nodes using walk. | ||
root.walk((node) => { | ||
if (isRoot(node)) { | ||
// Ignore nested template literals root in css-in-js lang | ||
return | ||
return `${count} ${quantifiedWarningWord}` | ||
} | ||
const nodeLevel = indentationLevel(node) | ||
// Cycle through all nodes using walk. | ||
root.walk((node) => { | ||
if (isRoot(node)) { | ||
// Ignore nested template literals root in css-in-js lang | ||
return | ||
} | ||
// Cut out any * and _ hacks from `before` | ||
const before = (node.raws.before || ``).replace(/[*_]$/, ``) | ||
const after = typeof node.raws.after === `string` ? node.raws.after : `` | ||
const parent = node.parent | ||
const nodeLevel = indentationLevel(node) | ||
if (!parent) {throw new Error(`A parent node must be present`)} | ||
// Cut out any * and _ hacks from `before` | ||
const before = (node.raws.before || ``).replace(/[*_]$/, ``) | ||
const after = typeof node.raws.after === `string` ? node.raws.after : `` | ||
const parent = node.parent | ||
const expectedOpeningBraceIndentation = indentChar.repeat(nodeLevel) | ||
if (!parent) { throw new Error(`A parent node must be present`) } | ||
// Only inspect the spaces before the node | ||
// if this is the first node in root | ||
// or there is a newline in the `before` string. | ||
// (If there is no newline before a node, | ||
// there is no "indentation" to check.) | ||
const isFirstChild = parent.type === `root` && parent.first === node | ||
const lastIndexOfNewline = before.lastIndexOf(`\n`) | ||
const expectedOpeningBraceIndentation = indentChar.repeat(nodeLevel) | ||
// Inspect whitespace in the `before` string that is | ||
// *after* the *last* newline character, | ||
// because anything besides that is not indentation for this node: | ||
// it is some other kind of separation, checked by some separate rule | ||
if ((lastIndexOfNewline !== -1 || (isFirstChild && (!getDocument(parent) || (parent.raws.codeBefore && parent.raws.codeBefore.endsWith(`\n`))))) && before.slice(lastIndexOfNewline + 1) !== expectedOpeningBraceIndentation) { | ||
if (context.fix) { | ||
if (isFirstChild && isString(node.raws.before)) { | ||
node.raws.before = node.raws.before.replace( | ||
/^[ \t]*(?=\S|$)/, | ||
expectedOpeningBraceIndentation, | ||
) | ||
// Only inspect the spaces before the node | ||
// if this is the first node in root | ||
// or there is a newline in the `before` string. | ||
// (If there is no newline before a node, | ||
// there is no "indentation" to check.) | ||
const isFirstChild = parent.type === `root` && parent.first === node | ||
const lastIndexOfNewline = before.lastIndexOf(`\n`) | ||
// Inspect whitespace in the `before` string that is | ||
// *after* the *last* newline character, | ||
// because anything besides that is not indentation for this node: | ||
// it is some other kind of separation, checked by some separate rule | ||
if ((lastIndexOfNewline !== -1 || (isFirstChild && (!getDocument(parent) || (parent.raws.codeBefore && parent.raws.codeBefore.endsWith(`\n`))))) && before.slice(lastIndexOfNewline + 1) !== expectedOpeningBraceIndentation) { | ||
if (context.fix) { | ||
if (isFirstChild && isString(node.raws.before)) { | ||
node.raws.before = node.raws.before.replace( | ||
/^[ \t]*(?=\S|$)/, | ||
expectedOpeningBraceIndentation, | ||
) | ||
} | ||
node.raws.before = fixIndentation(node.raws.before, expectedOpeningBraceIndentation) | ||
} else { | ||
report({ | ||
message: messages.expected(legibleExpectation(nodeLevel)), | ||
node, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
node.raws.before = fixIndentation(node.raws.before, expectedOpeningBraceIndentation) | ||
} else { | ||
report({ | ||
message: messages.expected(legibleExpectation(nodeLevel)), | ||
node, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
} | ||
// Only blocks have the `after` string to check. | ||
// Only inspect `after` strings that start with a newline; | ||
// otherwise there's no indentation involved. | ||
// And check `indentClosingBrace` to see if it should be indented an extra level. | ||
const closingBraceLevel = indentClosingBrace ? nodeLevel + 1 : nodeLevel | ||
const expectedClosingBraceIndentation = indentChar.repeat(closingBraceLevel) | ||
// Only blocks have the `after` string to check. | ||
// Only inspect `after` strings that start with a newline; | ||
// otherwise there's no indentation involved. | ||
// And check `indentClosingBrace` to see if it should be indented an extra level. | ||
const closingBraceLevel = indentClosingBrace ? nodeLevel + 1 : nodeLevel | ||
const expectedClosingBraceIndentation = indentChar.repeat(closingBraceLevel) | ||
if ((isRule(node) || isAtRule(node)) && hasBlock(node) && after && after.includes(`\n`) && after.slice(after.lastIndexOf(`\n`) + 1) !== expectedClosingBraceIndentation) { | ||
if (context.fix) { | ||
node.raws.after = fixIndentation(node.raws.after, expectedClosingBraceIndentation) | ||
} else { | ||
report({ | ||
message: messages.expected(legibleExpectation(closingBraceLevel)), | ||
node, | ||
index: node.toString().length - 1, | ||
result, | ||
ruleName, | ||
}) | ||
if ((isRule(node) || isAtRule(node)) && hasBlock(node) && after && after.includes(`\n`) && after.slice(after.lastIndexOf(`\n`) + 1) !== expectedClosingBraceIndentation) { | ||
if (context.fix) { | ||
node.raws.after = fixIndentation(node.raws.after, expectedClosingBraceIndentation) | ||
} else { | ||
report({ | ||
message: messages.expected(legibleExpectation(closingBraceLevel)), | ||
node, | ||
index: node.toString().length - 1, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
} | ||
} | ||
// If this is a declaration, check the value | ||
if (isDeclaration(node)) { | ||
checkValue(node, nodeLevel) | ||
} | ||
// If this is a declaration, check the value | ||
if (isDeclaration(node)) { | ||
checkValue(node, nodeLevel) | ||
} | ||
// If this is a rule, check the selector | ||
if (isRule(node)) { | ||
checkSelector(node, nodeLevel) | ||
} | ||
// If this is a rule, check the selector | ||
if (isRule(node)) { | ||
checkSelector(node, nodeLevel) | ||
} | ||
// If this is an at rule, check the params | ||
if (isAtRule(node)) { | ||
checkAtRuleParams(node, nodeLevel) | ||
} | ||
}) | ||
// If this is an at rule, check the params | ||
if (isAtRule(node)) { | ||
checkAtRuleParams(node, nodeLevel) | ||
} | ||
}) | ||
/** | ||
* @param {import('postcss').Node} node | ||
* @param {number} level | ||
* @returns {number} | ||
*/ | ||
function indentationLevel (node, level = 0) { | ||
if (!node.parent) {throw new Error(`A parent node must be present`)} | ||
/** | ||
* @param {import('postcss').Node} node | ||
* @param {number} level | ||
* @returns {number} | ||
*/ | ||
function indentationLevel (node, level = 0) { | ||
if (!node.parent) { throw new Error(`A parent node must be present`) } | ||
if (isRoot(node.parent)) { | ||
return level + getRootBaseIndentLevel(node.parent, baseIndentLevel, primary) | ||
} | ||
if (isRoot(node.parent)) { | ||
return level + getRootBaseIndentLevel(node.parent, baseIndentLevel, primary) | ||
} | ||
let calculatedLevel | ||
let calculatedLevel | ||
// Indentation level equals the ancestor nodes | ||
// separating this node from root; so recursively | ||
// run this operation | ||
calculatedLevel = indentationLevel(node.parent, level + 1) | ||
// Indentation level equals the ancestor nodes | ||
// separating this node from root; so recursively | ||
// run this operation | ||
calculatedLevel = indentationLevel(node.parent, level + 1) | ||
// If `secondaryOptions.except` includes "block", | ||
// blocks are taken down one from their calculated level | ||
// (all blocks are the same level as their parents) | ||
if (optionsMatches(secondaryOptions, `except`, `block`) && (isRule(node) || isAtRule(node)) && hasBlock(node)) { | ||
calculatedLevel-- | ||
} | ||
// If `secondaryOptions.except` includes "block", | ||
// blocks are taken down one from their calculated level | ||
// (all blocks are the same level as their parents) | ||
if (optionsMatches(secondaryOptions, `except`, `block`) && (isRule(node) || isAtRule(node)) && hasBlock(node)) { | ||
calculatedLevel-- | ||
} | ||
return calculatedLevel | ||
} | ||
/** | ||
* @param {import('postcss').Declaration} decl | ||
* @param {number} declLevel | ||
*/ | ||
function checkValue (decl, declLevel) { | ||
if (!decl.value.includes(`\n`)) { | ||
return | ||
return calculatedLevel | ||
} | ||
if (optionsMatches(secondaryOptions, `ignore`, `value`)) { | ||
return | ||
} | ||
/** | ||
* @param {import('postcss').Declaration} decl | ||
* @param {number} declLevel | ||
*/ | ||
function checkValue (decl, declLevel) { | ||
if (!decl.value.includes(`\n`)) { | ||
return | ||
} | ||
const declString = decl.toString() | ||
const valueLevel = optionsMatches(secondaryOptions, `except`, `value`) ? declLevel : declLevel + 1 | ||
if (optionsMatches(secondaryOptions, `ignore`, `value`)) { | ||
return | ||
} | ||
checkMultilineBit(declString, valueLevel, decl) | ||
} | ||
const declString = decl.toString() | ||
const valueLevel = optionsMatches(secondaryOptions, `except`, `value`) ? declLevel : declLevel + 1 | ||
/** | ||
* @param {import('postcss').Rule} ruleNode | ||
* @param {number} ruleLevel | ||
*/ | ||
function checkSelector (ruleNode, ruleLevel) { | ||
const selector = ruleNode.selector | ||
// Less mixins have params, and they should be indented extra | ||
// @ts-expect-error -- TS2339: Property 'params' does not exist on type 'Rule'. | ||
if (ruleNode.params) { | ||
ruleLevel += 1 | ||
checkMultilineBit(declString, valueLevel, decl) | ||
} | ||
checkMultilineBit(selector, ruleLevel, ruleNode) | ||
} | ||
/** | ||
* @param {import('postcss').Rule} ruleNode | ||
* @param {number} ruleLevel | ||
*/ | ||
function checkSelector (ruleNode, ruleLevel) { | ||
const selector = ruleNode.selector | ||
/** | ||
* @param {import('postcss').AtRule} atRule | ||
* @param {number} ruleLevel | ||
*/ | ||
function checkAtRuleParams (atRule, ruleLevel) { | ||
if (optionsMatches(secondaryOptions, `ignore`, `param`)) { | ||
return | ||
// Less mixins have params, and they should be indented extra | ||
// @ts-expect-error -- TS2339: Property 'params' does not exist on type 'Rule'. | ||
if (ruleNode.params) { | ||
ruleLevel += 1 | ||
} | ||
checkMultilineBit(selector, ruleLevel, ruleNode) | ||
} | ||
// @nest and SCSS's @at-root rules should be treated like regular rules, not expected | ||
// to have their params (selectors) indented | ||
const paramLevel = optionsMatches(secondaryOptions, `except`, `param`) || atRule.name === `nest` || atRule.name === `at-root` ? ruleLevel : ruleLevel + 1 | ||
/** | ||
* @param {import('postcss').AtRule} atRule | ||
* @param {number} ruleLevel | ||
*/ | ||
function checkAtRuleParams (atRule, ruleLevel) { | ||
if (optionsMatches(secondaryOptions, `ignore`, `param`)) { | ||
return | ||
} | ||
checkMultilineBit(beforeBlockString(atRule).trim(), paramLevel, atRule) | ||
} | ||
// @nest and SCSS's @at-root rules should be treated like regular rules, not expected | ||
// to have their params (selectors) indented | ||
const paramLevel = optionsMatches(secondaryOptions, `except`, `param`) || atRule.name === `nest` || atRule.name === `at-root` ? ruleLevel : ruleLevel + 1 | ||
/** | ||
* @param {string} source | ||
* @param {number} newlineIndentLevel | ||
* @param {import('postcss').Node} node | ||
*/ | ||
function checkMultilineBit (source, newlineIndentLevel, node) { | ||
if (!source.includes(`\n`)) { | ||
return | ||
checkMultilineBit(beforeBlockString(atRule).trim(), paramLevel, atRule) | ||
} | ||
// Data for current node fixing | ||
/** @type {Array<{ expectedIndentation: string, currentIndentation: string, startIndex: number }>} */ | ||
const fixPositions = [] | ||
/** | ||
* @param {string} source | ||
* @param {number} newlineIndentLevel | ||
* @param {import('postcss').Node} node | ||
*/ | ||
function checkMultilineBit (source, newlineIndentLevel, node) { | ||
if (!source.includes(`\n`)) { | ||
return | ||
} | ||
// `outsideParens` because function arguments and also non-standard parenthesized stuff like | ||
// Sass maps are ignored to allow for arbitrary indentation | ||
let parentheticalDepth = 0 | ||
// Data for current node fixing | ||
/** @type {Array<{ expectedIndentation: string, currentIndentation: string, startIndex: number }>} */ | ||
const fixPositions = [] | ||
const ignoreInsideParans = optionsMatches(secondaryOptions, `ignore`, `inside-parens`) | ||
// `outsideParens` because function arguments and also non-standard parenthesized stuff like | ||
// Sass maps are ignored to allow for arbitrary indentation | ||
let parentheticalDepth = 0 | ||
styleSearch( | ||
{ | ||
source, | ||
target: `\n`, | ||
// @ts-expect-error -- The `outsideParens` option is unsupported. Why? | ||
outsideParens: ignoreInsideParans, | ||
}, | ||
(match, matchCount) => { | ||
const precedesClosingParenthesis = /^[ \t]*\)/.test(source.slice(match.startIndex + 1)) | ||
const ignoreInsideParans = optionsMatches(secondaryOptions, `ignore`, `inside-parens`) | ||
if (ignoreInsideParans && (precedesClosingParenthesis || match.insideParens)) { | ||
return | ||
} | ||
styleSearch( | ||
{ | ||
source, | ||
target: `\n`, | ||
// @ts-expect-error -- The `outsideParens` option is unsupported. Why? | ||
outsideParens: ignoreInsideParans, | ||
}, | ||
(match, matchCount) => { | ||
const precedesClosingParenthesis = (/^[ \t]*\)/).test(source.slice(match.startIndex + 1)) | ||
let expectedIndentLevel = newlineIndentLevel | ||
// Modififications for parenthetical content | ||
if (!ignoreInsideParans && match.insideParens) { | ||
// If the first match in is within parentheses, reduce the parenthesis penalty | ||
if (matchCount === 1) {parentheticalDepth -= 1} | ||
// Account for windows line endings | ||
let newlineIndex = match.startIndex | ||
if (source[match.startIndex - 1] === `\r`) { | ||
newlineIndex-- | ||
if (ignoreInsideParans && (precedesClosingParenthesis || match.insideParens)) { | ||
return | ||
} | ||
const followsOpeningParenthesis = /\([ \t]*$/.test(source.slice(0, newlineIndex)) | ||
let expectedIndentLevel = newlineIndentLevel | ||
if (followsOpeningParenthesis) { | ||
parentheticalDepth += 1 | ||
} | ||
// Modififications for parenthetical content | ||
if (!ignoreInsideParans && match.insideParens) { | ||
// If the first match in is within parentheses, reduce the parenthesis penalty | ||
if (matchCount === 1) { parentheticalDepth -= 1 } | ||
const followsOpeningBrace = /\{[ \t]*$/.test(source.slice(0, newlineIndex)) | ||
// Account for windows line endings | ||
let newlineIndex = match.startIndex | ||
if (followsOpeningBrace) { | ||
parentheticalDepth += 1 | ||
} | ||
if (source[match.startIndex - 1] === `\r`) { | ||
newlineIndex-- | ||
} | ||
const startingClosingBrace = /^[ \t]*\}/.test(source.slice(match.startIndex + 1)) | ||
const followsOpeningParenthesis = (/\([ \t]*$/).test(source.slice(0, newlineIndex)) | ||
if (startingClosingBrace) { | ||
parentheticalDepth -= 1 | ||
} | ||
if (followsOpeningParenthesis) { | ||
parentheticalDepth += 1 | ||
} | ||
expectedIndentLevel += parentheticalDepth | ||
const followsOpeningBrace = (/\{[ \t]*$/).test(source.slice(0, newlineIndex)) | ||
// Past this point, adjustments to parentheticalDepth affect next line | ||
if (followsOpeningBrace) { | ||
parentheticalDepth += 1 | ||
} | ||
if (precedesClosingParenthesis) { | ||
parentheticalDepth -= 1 | ||
} | ||
const startingClosingBrace = (/^[ \t]*\}/).test(source.slice(match.startIndex + 1)) | ||
switch (secondaryOptions.indentInsideParens) { | ||
case `twice`: | ||
if (!precedesClosingParenthesis || indentClosingBrace) { | ||
expectedIndentLevel += 1 | ||
if (startingClosingBrace) { | ||
parentheticalDepth -= 1 | ||
} | ||
break | ||
case `once-at-root-twice-in-block`: | ||
if (node.parent === node.root()) { | ||
if (precedesClosingParenthesis && !indentClosingBrace) { | ||
expectedIndentLevel -= 1 | ||
} | ||
expectedIndentLevel += parentheticalDepth | ||
break | ||
// Past this point, adjustments to parentheticalDepth affect next line | ||
if (precedesClosingParenthesis) { | ||
parentheticalDepth -= 1 | ||
} | ||
if (!precedesClosingParenthesis || indentClosingBrace) { | ||
expectedIndentLevel += 1 | ||
} | ||
switch (secondaryOptions.indentInsideParens) { | ||
case `twice`: | ||
if (!precedesClosingParenthesis || indentClosingBrace) { | ||
expectedIndentLevel += 1 | ||
} | ||
break | ||
default: | ||
if (precedesClosingParenthesis && !indentClosingBrace) { | ||
expectedIndentLevel -= 1 | ||
break | ||
case `once-at-root-twice-in-block`: | ||
if (node.parent === node.root()) { | ||
if (precedesClosingParenthesis && !indentClosingBrace) { | ||
expectedIndentLevel -= 1 | ||
} | ||
break | ||
} | ||
if (!precedesClosingParenthesis || indentClosingBrace) { | ||
expectedIndentLevel += 1 | ||
} | ||
break | ||
default: | ||
if (precedesClosingParenthesis && !indentClosingBrace) { | ||
expectedIndentLevel -= 1 | ||
} | ||
} | ||
} | ||
} | ||
// Starting at the index after the newline, we want to | ||
// check that the whitespace characters (excluding newlines) before the first | ||
// non-whitespace character equal the expected indentation | ||
const afterNewlineSpaceMatches = /^([ \t]*)\S/.exec(source.slice(match.startIndex + 1)) | ||
// Starting at the index after the newline, we want to | ||
// check that the whitespace characters (excluding newlines) before the first | ||
// non-whitespace character equal the expected indentation | ||
const afterNewlineSpaceMatches = (/^([ \t]*)\S/).exec(source.slice(match.startIndex + 1)) | ||
if (!afterNewlineSpaceMatches) { | ||
return | ||
} | ||
const afterNewlineSpace = afterNewlineSpaceMatches[1] || `` | ||
const expectedIndentation = indentChar.repeat( | ||
expectedIndentLevel > 0 ? expectedIndentLevel : 0, | ||
) | ||
if (afterNewlineSpace !== expectedIndentation) { | ||
if (context.fix) { | ||
// Adding fixes position in reverse order, because if we change indent in the beginning of the string it will break all following fixes for that string | ||
fixPositions.unshift({ | ||
expectedIndentation, | ||
currentIndentation: afterNewlineSpace, | ||
startIndex: match.startIndex, | ||
}) | ||
} else { | ||
report({ | ||
message: messages.expected(legibleExpectation(expectedIndentLevel)), | ||
node, | ||
index: match.startIndex + afterNewlineSpace.length + 1, | ||
result, | ||
ruleName, | ||
}) | ||
if (!afterNewlineSpaceMatches) { | ||
return | ||
} | ||
} | ||
}, | ||
) | ||
if (fixPositions.length) { | ||
if (isRule(node)) { | ||
for (const fixPosition of fixPositions) { | ||
node.selector = replaceIndentation( | ||
node.selector, | ||
fixPosition.currentIndentation, | ||
fixPosition.expectedIndentation, | ||
fixPosition.startIndex, | ||
const afterNewlineSpace = afterNewlineSpaceMatches[1] || `` | ||
const expectedIndentation = indentChar.repeat( | ||
expectedIndentLevel > 0 ? expectedIndentLevel : 0, | ||
) | ||
} | ||
} | ||
if (isDeclaration(node)) { | ||
const declProp = node.prop | ||
const declBetween = node.raws.between | ||
if (afterNewlineSpace !== expectedIndentation) { | ||
if (context.fix) { | ||
// Adding fixes position in reverse order, because if we change indent in the beginning of the string it will break all following fixes for that string | ||
fixPositions.unshift({ | ||
expectedIndentation, | ||
currentIndentation: afterNewlineSpace, | ||
startIndex: match.startIndex, | ||
}) | ||
} else { | ||
report({ | ||
message: messages.expected(legibleExpectation(expectedIndentLevel)), | ||
node, | ||
index: match.startIndex + afterNewlineSpace.length + 1, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
} | ||
}, | ||
) | ||
if (!isString(declBetween)) { | ||
throw new TypeError(`The \`between\` property must be a string`) | ||
} | ||
for (const fixPosition of fixPositions) { | ||
if (fixPosition.startIndex < declProp.length + declBetween.length) { | ||
node.raws.between = replaceIndentation( | ||
declBetween, | ||
if (fixPositions.length) { | ||
if (isRule(node)) { | ||
for (const fixPosition of fixPositions) { | ||
node.selector = replaceIndentation( | ||
node.selector, | ||
fixPosition.currentIndentation, | ||
fixPosition.expectedIndentation, | ||
fixPosition.startIndex - declProp.length, | ||
fixPosition.startIndex, | ||
) | ||
} else { | ||
node.value = replaceIndentation( | ||
node.value, | ||
fixPosition.currentIndentation, | ||
fixPosition.expectedIndentation, | ||
fixPosition.startIndex - declProp.length - declBetween.length, | ||
) | ||
} | ||
} | ||
} | ||
if (isAtRule(node)) { | ||
const atRuleName = node.name | ||
const atRuleAfterName = node.raws.afterName | ||
const atRuleParams = node.params | ||
if (isDeclaration(node)) { | ||
const declProp = node.prop | ||
const declBetween = node.raws.between | ||
if (!isString(atRuleAfterName)) { | ||
throw new TypeError(`The \`afterName\` property must be a string`) | ||
if (!isString(declBetween)) { | ||
throw new TypeError(`The \`between\` property must be a string`) | ||
} | ||
for (const fixPosition of fixPositions) { | ||
if (fixPosition.startIndex < declProp.length + declBetween.length) { | ||
node.raws.between = replaceIndentation( | ||
declBetween, | ||
fixPosition.currentIndentation, | ||
fixPosition.expectedIndentation, | ||
fixPosition.startIndex - declProp.length, | ||
) | ||
} else { | ||
node.value = replaceIndentation( | ||
node.value, | ||
fixPosition.currentIndentation, | ||
fixPosition.expectedIndentation, | ||
fixPosition.startIndex - declProp.length - declBetween.length, | ||
) | ||
} | ||
} | ||
} | ||
for (const fixPosition of fixPositions) { | ||
// 1 — it's a @ length | ||
if (fixPosition.startIndex < 1 + atRuleName.length + atRuleAfterName.length) { | ||
node.raws.afterName = replaceIndentation( | ||
atRuleAfterName, | ||
fixPosition.currentIndentation, | ||
fixPosition.expectedIndentation, | ||
fixPosition.startIndex - atRuleName.length - 1, | ||
) | ||
} else { | ||
node.params = replaceIndentation( | ||
atRuleParams, | ||
fixPosition.currentIndentation, | ||
fixPosition.expectedIndentation, | ||
fixPosition.startIndex - atRuleName.length - atRuleAfterName.length - 1, | ||
) | ||
if (isAtRule(node)) { | ||
const atRuleName = node.name | ||
const atRuleAfterName = node.raws.afterName | ||
const atRuleParams = node.params | ||
if (!isString(atRuleAfterName)) { | ||
throw new TypeError(`The \`afterName\` property must be a string`) | ||
} | ||
for (const fixPosition of fixPositions) { | ||
// 1 — it's a @ length | ||
if (fixPosition.startIndex < 1 + atRuleName.length + atRuleAfterName.length) { | ||
node.raws.afterName = replaceIndentation( | ||
atRuleAfterName, | ||
fixPosition.currentIndentation, | ||
fixPosition.expectedIndentation, | ||
fixPosition.startIndex - atRuleName.length - 1, | ||
) | ||
} else { | ||
node.params = replaceIndentation( | ||
atRuleParams, | ||
fixPosition.currentIndentation, | ||
fixPosition.expectedIndentation, | ||
fixPosition.startIndex - atRuleName.length - atRuleAfterName.length - 1, | ||
) | ||
} | ||
} | ||
} | ||
@@ -504,3 +507,3 @@ } | ||
function inferDocIndentSize (document, space) { | ||
if (!document.source) {throw new Error(`The document node must have a source`)} | ||
if (!document.source) { throw new Error(`The document node must have a source`) } | ||
@@ -521,3 +524,3 @@ /** @type {import('postcss').Source & { indentSize?: number }} */ | ||
/** @type {Map<number, number>} */ | ||
const scores = new Map() | ||
const scores = (new Map) | ||
let lastIndentSize = 0 | ||
@@ -529,3 +532,3 @@ let lastLeadingSpacesLength = 0 | ||
*/ | ||
const vote = (leadingSpacesLength) => { | ||
function vote (leadingSpacesLength) { | ||
if (leadingSpacesLength) { | ||
@@ -593,3 +596,3 @@ lastIndentSize = Math.abs(leadingSpacesLength - lastLeadingSpacesLength) || lastIndentSize | ||
if (!isNumber(baseIndentLevel) || !Number.isSafeInteger(baseIndentLevel)) { | ||
if (!root.source) {throw new Error(`The root node must have a source`)} | ||
if (!root.source) { throw new Error(`The root node must have a source`) } | ||
@@ -599,3 +602,3 @@ let source = root.source.input.css | ||
source = source.replace(/^[^\r\n]+/, (firstLine) => { | ||
const match = root.raws.codeBefore && /(?:^|\n)([ \t]*)$/.exec(root.raws.codeBefore) | ||
const match = root.raws.codeBefore && (/(?:^|\n)([ \t]*)$/).exec(root.raws.codeBefore) | ||
@@ -621,3 +624,3 @@ if (match) { | ||
const indents = [] | ||
const foundIndents = root.raws.codeBefore && /(?:^|\n)([ \t]*)\S/m.exec(root.raws.codeBefore) | ||
const foundIndents = root.raws.codeBefore && (/(?:^|\n)([ \t]*)\S/m).exec(root.raws.codeBefore) | ||
@@ -633,2 +636,3 @@ // The indent level of the CSS code block in non-CSS-like files is determined by the shortest indent of non-empty line. | ||
assertString(foundIndent) | ||
const current = getIndentLevel(foundIndent) | ||
@@ -667,3 +671,3 @@ | ||
if (!parent) {throw new Error(`The root node must have a parent`)} | ||
if (!parent) { throw new Error(`The root node must have a parent`) } | ||
@@ -678,3 +682,3 @@ const nextRoot = parent.nodes[parent.nodes.indexOf(root) + 1] | ||
if (afterEnd) {indents.push(afterEnd.match(/^[ \t]*/)[0])} | ||
if (afterEnd) { indents.push(afterEnd.match(/^[ \t]*/)[0]) } | ||
} | ||
@@ -718,2 +722,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { rule as _rule, Input } from "postcss" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
@@ -22,105 +23,107 @@ import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`unix`, `windows`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`unix`, `windows`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
if (!validOptions) { | ||
return | ||
} | ||
const shouldHaveCR = primary === `windows` | ||
const shouldHaveCR = primary === `windows` | ||
if (context.fix) { | ||
root.walk((node) => { | ||
if (`selector` in node) { | ||
node.selector = fixData(node.selector) | ||
} | ||
if (context.fix) { | ||
root.walk((node) => { | ||
if (`selector` in node) { | ||
node.selector = fixData(node.selector) | ||
} | ||
if (`value` in node) { | ||
node.value = fixData(node.value) | ||
} | ||
if (`value` in node) { | ||
node.value = fixData(node.value) | ||
} | ||
if (`text` in node) { | ||
node.text = fixData(node.text) | ||
} | ||
if (`text` in node) { | ||
node.text = fixData(node.text) | ||
} | ||
if (node.raws.before) { | ||
node.raws.before = fixData(node.raws.before) | ||
} | ||
if (node.raws.before) { | ||
node.raws.before = fixData(node.raws.before) | ||
} | ||
if (typeof node.raws.after === `string`) { | ||
node.raws.after = fixData(node.raws.after) | ||
if (typeof node.raws.after === `string`) { | ||
node.raws.after = fixData(node.raws.after) | ||
} | ||
}) | ||
if (typeof root.raws.after === `string`) { | ||
root.raws.after = fixData(root.raws.after) | ||
} | ||
}) | ||
} else { | ||
if (root.source === null) { throw new Error(`The root node must have a source`) } | ||
if (typeof root.raws.after === `string`) { | ||
root.raws.after = fixData(root.raws.after) | ||
} | ||
} else { | ||
if (root.source === null) {throw new Error(`The root node must have a source`)} | ||
const lines = root.source.input.css.split(`\n`) | ||
const lines = root.source.input.css.split(`\n`) | ||
for (let [i, line] of lines.entries()) { | ||
if (i < lines.length - 1 && !line.includes(`\r`)) { | ||
line += `\n` | ||
} | ||
for (let [i, line] of lines.entries()) { | ||
if (i < lines.length - 1 && !line.includes(`\r`)) { | ||
line += `\n` | ||
if (hasError(line)) { | ||
const lineNum = i + 1 | ||
const colNum = line.length | ||
reportNewlineError(lineNum, colNum) | ||
} | ||
} | ||
} | ||
if (hasError(line)) { | ||
const lineNum = i + 1 | ||
const colNum = line.length | ||
/** | ||
* @param {string} dataToCheck | ||
*/ | ||
function hasError (dataToCheck) { | ||
const hasNewlineToVerify = (/[\r\n]/).test(dataToCheck) | ||
const hasCR = hasNewlineToVerify ? (/\r/).test(dataToCheck) : false | ||
reportNewlineError(lineNum, colNum) | ||
} | ||
return hasNewlineToVerify && hasCR !== shouldHaveCR | ||
} | ||
} | ||
/** | ||
* @param {string} dataToCheck | ||
*/ | ||
function hasError (dataToCheck) { | ||
const hasNewlineToVerify = /[\r\n]/.test(dataToCheck) | ||
const hasCR = hasNewlineToVerify ? /\r/.test(dataToCheck) : false | ||
/** | ||
* @param {string} data | ||
*/ | ||
function fixData (data) { | ||
if (data) { | ||
let res = data.replace(/\r/g, ``) | ||
return hasNewlineToVerify && hasCR !== shouldHaveCR | ||
} | ||
if (shouldHaveCR) { | ||
res = res.replace(/\n/g, `\r\n`) | ||
} | ||
/** | ||
* @param {string} data | ||
*/ | ||
function fixData (data) { | ||
if (data) { | ||
let res = data.replace(/\r/g, ``) | ||
if (shouldHaveCR) { | ||
res = res.replace(/\n/g, `\r\n`) | ||
return res | ||
} | ||
return res | ||
return data | ||
} | ||
return data | ||
} | ||
/** | ||
* @param {number} line | ||
* @param {number} column | ||
*/ | ||
function reportNewlineError (line, column) { | ||
// Creating a node manually helps us to point to empty lines. | ||
const node = _rule({ | ||
source: { | ||
start: { line, column, offset: 0 }, | ||
input: new Input(``), | ||
}, | ||
}) | ||
/** | ||
* @param {number} line | ||
* @param {number} column | ||
*/ | ||
function reportNewlineError (line, column) { | ||
// Creating a node manually helps us to point to empty lines. | ||
const node = _rule({ | ||
source: { | ||
start: { line, column, offset: 0 }, | ||
input: new Input(``), | ||
}, | ||
}) | ||
report({ | ||
message: messages.expected(primary), | ||
node, | ||
result, | ||
ruleName, | ||
}) | ||
report({ | ||
message: messages.expected(primary), | ||
node, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
} | ||
@@ -132,2 +135,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import styleSearch from "style-search" | ||
import optionsMatches from "../../utils/optionsMatches.js" | ||
import { isNumber } from "../../utils/validateTypes.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import { isNumber } from "../../utils/validateTypes.js" | ||
import optionsMatches from "../../utils/optionsMatches.js" | ||
@@ -25,3 +25,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions, context) => { | ||
function rule (primary, secondaryOptions, context) { | ||
let emptyLines = 0 | ||
@@ -96,2 +96,3 @@ let lastIndex = -1 | ||
lastIndex = -1 | ||
const rootString = root.toString() | ||
@@ -102,3 +103,3 @@ | ||
source: rootString, | ||
target: /\r\n/.test(rootString) ? `\r\n` : `\n`, | ||
target: (/\r\n/).test(rootString) ? `\r\n` : `\n`, | ||
comments: ignoreComments ? `skip` : `check`, | ||
@@ -130,5 +131,5 @@ }, | ||
if (emptyLines > primary) {problem = true} | ||
if (emptyLines > primary) { problem = true } | ||
if (!eof && !problem) {return} | ||
if (!eof && !problem) { return } | ||
@@ -176,15 +177,17 @@ if (problem) { | ||
return /(?:\r\n)+/.test(str) ? str.replace(/(\r\n)+/g, ($1) => { | ||
if ($1.length / 2 > repeatTimes) { | ||
return emptyCRLFLines | ||
} | ||
return (/(?:\r\n)+/).test(str) | ||
? str.replace(/(\r\n)+/g, ($1) => { | ||
if ($1.length / 2 > repeatTimes) { | ||
return emptyCRLFLines | ||
} | ||
return $1 | ||
}) : str.replace(/(\n)+/g, ($1) => { | ||
if ($1.length > repeatTimes) { | ||
return emptyLFLines | ||
} | ||
return $1 | ||
}) | ||
: str.replace(/(\n)+/g, ($1) => { | ||
if ($1.length > repeatTimes) { | ||
return emptyLFLines | ||
} | ||
return $1 | ||
}) | ||
return $1 | ||
}) | ||
} | ||
@@ -224,2 +227,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import styleSearch from "style-search" | ||
import optionsMatches from "../../utils/optionsMatches.js" | ||
import { isNumber, isRegExp, isString, assert } from "../../utils/validateTypes.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import optionsMatches from "../../utils/optionsMatches.js" | ||
import { assert, isNumber, isRegExp, isString } from "../../utils/validateTypes.js" | ||
@@ -16,4 +16,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
export const messages = ruleMessages(ruleName, { | ||
expected: (max) => | ||
`Expected line length to be no more than ${max} ${max === 1 ? `character` : `characters`}`, | ||
expected: (max) => `Expected line length to be no more than ${max} ${max === 1 ? `character` : `characters`}`, | ||
}) | ||
@@ -26,158 +25,168 @@ | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
possible: isNumber, | ||
}, | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
ignore: [`non-comments`, `comments`], | ||
ignorePattern: [isString, isRegExp], | ||
function rule (primary, secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
possible: isNumber, | ||
}, | ||
optional: true, | ||
}, | ||
) | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
ignore: [`non-comments`, `comments`], | ||
ignorePattern: [isString, isRegExp], | ||
}, | ||
optional: true, | ||
}, | ||
) | ||
if (!validOptions) { | ||
return | ||
} | ||
if (!validOptions) { | ||
return | ||
} | ||
if (root.source === null) { | ||
throw new Error(`The root node must have a source`) | ||
} | ||
if (root.source === null) { | ||
throw new Error(`The root node must have a source`) | ||
} | ||
const EXCLUDED_PATTERNS = [ | ||
/url\(\s*(\S.*\S)\s*\)/gi, // allow tab, whitespace in url content | ||
/@import\s+(['"].*['"])/gi, | ||
] | ||
const EXCLUDED_PATTERNS = [ | ||
/url\(\s*(\S.*\S)\s*\)/gi, // allow tab, whitespace in url content | ||
/@import\s+(['"].*['"])/gi, | ||
] | ||
const ignoreNonComments = optionsMatches(secondaryOptions, `ignore`, `non-comments`) | ||
const ignoreComments = optionsMatches(secondaryOptions, `ignore`, `comments`) | ||
const rootString = context.fix ? root.toString() : root.source.input.css | ||
// Array of skipped sub strings, i.e `url(...)`, `@import "..."` | ||
/** @type {Array<[number, number]>} */ | ||
let skippedSubStrings = [] | ||
let skippedSubStringsIndex = 0 | ||
const ignoreNonComments = optionsMatches(secondaryOptions, `ignore`, `non-comments`) | ||
const ignoreComments = optionsMatches(secondaryOptions, `ignore`, `comments`) | ||
const rootString = context.fix ? root.toString() : root.source.input.css | ||
// Array of skipped sub strings, i.e `url(...)`, `@import "..."` | ||
/** @type {Array<[number, number]>} */ | ||
let skippedSubStrings = [] | ||
let skippedSubStringsIndex = 0 | ||
for (const pattern of EXCLUDED_PATTERNS) { | ||
for (const match of rootString.matchAll(pattern)) { | ||
const subMatch = match[1] || `` | ||
const startOfSubString = (match.index || 0) + (match[0] || ``).indexOf(subMatch) | ||
for (const pattern of EXCLUDED_PATTERNS) { | ||
for (const match of rootString.matchAll(pattern)) { | ||
const subMatch = match[1] || `` | ||
const startOfSubString = (match.index || 0) + (match[0] || ``).indexOf(subMatch) | ||
skippedSubStrings.push([startOfSubString, startOfSubString + subMatch.length]) | ||
skippedSubStrings.push([startOfSubString, startOfSubString + subMatch.length]) | ||
} | ||
} | ||
} | ||
skippedSubStrings = skippedSubStrings.sort((a, b) => a[0] - b[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), | ||
) | ||
// Check first line | ||
checkNewline({ endIndex: 0 }) | ||
// Check subsequent lines | ||
styleSearch({ source: rootString, target: [`\n`], comments: `check` }, (match) => checkNewline(match)) | ||
/** | ||
* @param {number} index | ||
*/ | ||
function complain (index) { | ||
report({ | ||
index, | ||
result, | ||
ruleName, | ||
message: messages.expected(primary), | ||
node: root, | ||
}) | ||
} | ||
/** | ||
* @param {number} index | ||
*/ | ||
function complain (index) { | ||
report({ | ||
index, | ||
result, | ||
ruleName, | ||
message: messages.expected(primary), | ||
node: root, | ||
}) | ||
} | ||
/** | ||
* @param {number} start | ||
* @param {number} end | ||
*/ | ||
function tryToPopSubString (start, end) { | ||
const skippedSubString = skippedSubStrings[skippedSubStringsIndex] | ||
/** | ||
* @param {number} start | ||
* @param {number} end | ||
*/ | ||
function tryToPopSubString (start, end) { | ||
const skippedSubString = skippedSubStrings[skippedSubStringsIndex] | ||
assert(skippedSubString) | ||
const [startSubString, endSubString] = skippedSubString | ||
assert(skippedSubString) | ||
// Excluded substring does not presented in current line | ||
if (end < startSubString) { | ||
return 0 | ||
} | ||
const [startSubString, endSubString] = skippedSubString | ||
// Compute excluded substring size regarding to current line indexes | ||
const excluded = Math.min(end, endSubString) - Math.max(start, startSubString) | ||
// Excluded substring does not presented in current line | ||
if (end < startSubString) { | ||
return 0 | ||
} | ||
// Current substring is out of range for next lines | ||
if (endSubString <= end) { | ||
skippedSubStringsIndex++ | ||
} | ||
// Compute excluded substring size regarding to current line indexes | ||
const excluded = Math.min(end, endSubString) - Math.max(start, startSubString) | ||
return excluded | ||
} | ||
// Current substring is out of range for next lines | ||
if (endSubString <= end) { | ||
skippedSubStringsIndex++ | ||
} | ||
/** | ||
* @param {import('style-search').StyleSearchMatch | { endIndex: number }} match | ||
*/ | ||
function checkNewline (match) { | ||
let nextNewlineIndex = rootString.indexOf(`\n`, match.endIndex) | ||
if (rootString[nextNewlineIndex - 1] === `\r`) { | ||
nextNewlineIndex -= 1 | ||
return excluded | ||
} | ||
// Accommodate last line | ||
if (nextNewlineIndex === -1) { | ||
nextNewlineIndex = rootString.length | ||
} | ||
/** | ||
* @param {import('style-search').StyleSearchMatch | { endIndex: number }} match | ||
*/ | ||
function checkNewline (match) { | ||
let nextNewlineIndex = rootString.indexOf(`\n`, match.endIndex) | ||
const rawLineLength = nextNewlineIndex - match.endIndex | ||
const excludedLength = skippedSubStrings[skippedSubStringsIndex] ? tryToPopSubString(match.endIndex, nextNewlineIndex) : 0 | ||
const lineText = rootString.slice(match.endIndex, nextNewlineIndex) | ||
if (rootString[nextNewlineIndex - 1] === `\r`) { | ||
nextNewlineIndex -= 1 | ||
} | ||
// Case sensitive ignorePattern match | ||
if (optionsMatches(secondaryOptions, `ignorePattern`, lineText)) { | ||
return | ||
} | ||
// Accommodate last line | ||
if (nextNewlineIndex === -1) { | ||
nextNewlineIndex = rootString.length | ||
} | ||
// 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 <= primary) { | ||
return | ||
} | ||
const rawLineLength = nextNewlineIndex - match.endIndex | ||
const excludedLength = skippedSubStrings[skippedSubStringsIndex] ? tryToPopSubString(match.endIndex, nextNewlineIndex) : 0 | ||
const lineText = rootString.slice(match.endIndex, nextNewlineIndex) | ||
const complaintIndex = nextNewlineIndex - 1 | ||
// Case sensitive ignorePattern match | ||
if (optionsMatches(secondaryOptions, `ignorePattern`, lineText)) { | ||
return | ||
} | ||
if (ignoreComments) { | ||
if (`insideComment` in match && match.insideComment) { | ||
// 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 <= primary) { | ||
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) | ||
const complaintIndex = nextNewlineIndex - 1 | ||
if (nextTwoChars === `/*` || nextTwoChars === `//`) { | ||
return | ||
if (ignoreComments) { | ||
if (`insideComment` in match && 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 (`insideComment` in match && match.insideComment) { | ||
if (ignoreNonComments) { | ||
if (`insideComment` in match && 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) | ||
} | ||
// 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 there are no spaces besides initial (indent) spaces, ignore it | ||
const lineString = rootString.slice(match.endIndex, nextNewlineIndex) | ||
if (nextTwoChars !== `/*` && nextTwoChars !== `//`) { | ||
if (!lineString.replace(/^\s+/, ``).includes(` `)) { | ||
return | ||
@@ -188,11 +197,2 @@ } | ||
} | ||
// 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) | ||
} | ||
@@ -204,2 +204,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import mediaFeatureColonSpaceChecker from "../../utils/mediaFeatureColonSpaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -26,3 +26,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -48,13 +48,16 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (atRule, index) => { | ||
const paramColonIndex = index - atRuleParamIndex(atRule) | ||
fix: context.fix | ||
? (atRule, index) => { | ||
const paramColonIndex = index - atRuleParamIndex(atRule) | ||
fixData = fixData || new Map() | ||
const colonIndices = fixData.get(atRule) || [] | ||
fixData = fixData || (new Map) | ||
colonIndices.push(paramColonIndex) | ||
fixData.set(atRule, colonIndices) | ||
const colonIndices = fixData.get(atRule) || [] | ||
return true | ||
} : null, | ||
colonIndices.push(paramColonIndex) | ||
fixData.set(atRule, colonIndices) | ||
return true | ||
} | ||
: null, | ||
}) | ||
@@ -90,2 +93,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import mediaFeatureColonSpaceChecker from "../../utils/mediaFeatureColonSpaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -26,3 +26,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -48,13 +48,16 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (atRule, index) => { | ||
const paramColonIndex = index - atRuleParamIndex(atRule) | ||
fix: context.fix | ||
? (atRule, index) => { | ||
const paramColonIndex = index - atRuleParamIndex(atRule) | ||
fixData = fixData || new Map() | ||
const colonIndices = fixData.get(atRule) || [] | ||
fixData = fixData || (new Map) | ||
colonIndices.push(paramColonIndex) | ||
fixData.set(atRule, colonIndices) | ||
const colonIndices = fixData.get(atRule) || [] | ||
return true | ||
} : null, | ||
colonIndices.push(paramColonIndex) | ||
fixData.set(atRule, colonIndices) | ||
return true | ||
} | ||
: null, | ||
}) | ||
@@ -90,2 +93,3 @@ | ||
rule.meta = meta | ||
export default rule |
@@ -0,9 +1,9 @@ | ||
import { mutateIdent } from "@csstools/css-tokenizer" | ||
import stylelint from "stylelint" | ||
import { mutateIdent } from "@csstools/css-tokenizer" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import findMediaFeatureNames from "../../utils/findMediaFeatureNames.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isCustomMediaQuery from "../../utils/isCustomMediaQuery.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -26,62 +26,64 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
if (!validOptions) { | ||
return | ||
} | ||
root.walkAtRules(/^media$/i, (atRule) => { | ||
let hasComments = atRule.raws.params?.raw | ||
let mediaRule = hasComments ? hasComments : atRule.params | ||
root.walkAtRules(/^media$/i, (atRule) => { | ||
let hasComments = atRule.raws.params?.raw | ||
let mediaRule = hasComments ? hasComments : atRule.params | ||
let hasFixes = false | ||
let hasFixes = false | ||
mediaRule = findMediaFeatureNames(mediaRule, (mediaFeatureNameToken) => { | ||
const [, , startIndex, endIndex, { value: featureName }] = mediaFeatureNameToken | ||
mediaRule = findMediaFeatureNames(mediaRule, (mediaFeatureNameToken) => { | ||
const [, , startIndex, endIndex, { value: featureName }] = mediaFeatureNameToken | ||
if (isCustomMediaQuery(featureName)) { | ||
return | ||
} | ||
if (isCustomMediaQuery(featureName)) { | ||
return | ||
} | ||
const expectedFeatureName = primary === `lower` ? featureName.toLowerCase() : featureName.toUpperCase() | ||
const expectedFeatureName = primary === `lower` ? featureName.toLowerCase() : featureName.toUpperCase() | ||
if (featureName === expectedFeatureName) { | ||
return | ||
} | ||
if (featureName === expectedFeatureName) { | ||
return | ||
} | ||
if (context.fix) { | ||
mutateIdent(mediaFeatureNameToken, expectedFeatureName) | ||
hasFixes = true | ||
if (context.fix) { | ||
mutateIdent(mediaFeatureNameToken, expectedFeatureName) | ||
hasFixes = true | ||
return | ||
} | ||
return | ||
} | ||
const atRuleIndex = atRuleParamIndex(atRule) | ||
const atRuleIndex = atRuleParamIndex(atRule) | ||
report({ | ||
message: messages.expected(featureName, expectedFeatureName), | ||
node: atRule, | ||
index: atRuleIndex + startIndex, | ||
endIndex: atRuleIndex + endIndex + 1, | ||
ruleName, | ||
result, | ||
}) | ||
}).stringify() | ||
report({ | ||
message: messages.expected(featureName, expectedFeatureName), | ||
node: atRule, | ||
index: atRuleIndex + startIndex, | ||
endIndex: atRuleIndex + endIndex + 1, | ||
ruleName, | ||
result, | ||
}) | ||
}).stringify() | ||
if (hasFixes) { | ||
if (hasComments) { | ||
if (atRule.raws.params === null) { | ||
throw new Error(`The \`AtRuleRaws\` node must have a \`params\` property`) | ||
if (hasFixes) { | ||
if (hasComments) { | ||
if (atRule.raws.params === null) { | ||
throw new Error(`The \`AtRuleRaws\` node must have a \`params\` property`) | ||
} | ||
atRule.raws.params.raw = mediaRule | ||
} else { | ||
atRule.params = mediaRule | ||
} | ||
atRule.raws.params.raw = mediaRule | ||
} else { | ||
atRule.params = mediaRule | ||
} | ||
} | ||
}) | ||
}) | ||
} | ||
} | ||
@@ -92,2 +94,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import valueParser from "postcss-value-parser" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,82 +27,85 @@ | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
if (!validOptions) { | ||
return | ||
} | ||
root.walkAtRules(/^media$/i, (atRule) => { | ||
// If there are comments in the params, the complete string | ||
// will be at atRule.raws.params.raw | ||
const params = (atRule.raws.params && atRule.raws.params.raw) || atRule.params | ||
const indexBoost = atRuleParamIndex(atRule) | ||
/** @type {Array<{ message: string, index: number }>} */ | ||
const problems = [] | ||
root.walkAtRules(/^media$/i, (atRule) => { | ||
// If there are comments in the params, the complete string | ||
// will be at atRule.raws.params.raw | ||
const params = (atRule.raws.params && atRule.raws.params.raw) || atRule.params | ||
const indexBoost = atRuleParamIndex(atRule) | ||
const parsedParams = valueParser(params).walk((node) => { | ||
if (node.type === `function`) { | ||
const len = valueParser.stringify(node).length | ||
/** @type {Array<{ message: string, index: number }>} */ | ||
const problems = [] | ||
if (primary === `never`) { | ||
if (/[ \t]/.test(node.before)) { | ||
if (context.fix) {node.before = ``} | ||
const parsedParams = valueParser(params).walk((node) => { | ||
if (node.type === `function`) { | ||
const len = valueParser.stringify(node).length | ||
problems.push({ | ||
message: messages.rejectedOpening, | ||
index: node.sourceIndex + 1 + indexBoost, | ||
}) | ||
} | ||
if (primary === `never`) { | ||
if ((/[ \t]/).test(node.before)) { | ||
if (context.fix) { node.before = `` } | ||
if (/[ \t]/.test(node.after)) { | ||
if (context.fix) {node.after = ``} | ||
problems.push({ | ||
message: messages.rejectedOpening, | ||
index: node.sourceIndex + 1 + indexBoost, | ||
}) | ||
} | ||
problems.push({ | ||
message: messages.rejectedClosing, | ||
index: node.sourceIndex - 2 + len + indexBoost, | ||
}) | ||
} | ||
} else if (primary === `always`) { | ||
if (node.before === ``) { | ||
if (context.fix) {node.before = ` `} | ||
if ((/[ \t]/).test(node.after)) { | ||
if (context.fix) { node.after = `` } | ||
problems.push({ | ||
message: messages.expectedOpening, | ||
index: node.sourceIndex + 1 + indexBoost, | ||
}) | ||
} | ||
problems.push({ | ||
message: messages.rejectedClosing, | ||
index: node.sourceIndex - 2 + len + indexBoost, | ||
}) | ||
} | ||
} else if (primary === `always`) { | ||
if (node.before === ``) { | ||
if (context.fix) { node.before = ` ` } | ||
if (node.after === ``) { | ||
if (context.fix) {node.after = ` `} | ||
problems.push({ | ||
message: messages.expectedOpening, | ||
index: node.sourceIndex + 1 + indexBoost, | ||
}) | ||
} | ||
problems.push({ | ||
message: messages.expectedClosing, | ||
index: node.sourceIndex - 2 + len + indexBoost, | ||
}) | ||
if (node.after === ``) { | ||
if (context.fix) { node.after = ` ` } | ||
problems.push({ | ||
message: messages.expectedClosing, | ||
index: node.sourceIndex - 2 + len + indexBoost, | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
}) | ||
}) | ||
if (problems.length) { | ||
if (context.fix) { | ||
atRule.params = parsedParams.toString() | ||
if (problems.length) { | ||
if (context.fix) { | ||
atRule.params = parsedParams.toString() | ||
return | ||
} | ||
return | ||
} | ||
for (const err of problems) { | ||
report({ | ||
message: err.message, | ||
node: atRule, | ||
index: err.index, | ||
result, | ||
ruleName, | ||
}) | ||
for (const err of problems) { | ||
report({ | ||
message: err.message, | ||
node: atRule, | ||
index: err.index, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
} | ||
} | ||
}) | ||
}) | ||
} | ||
} | ||
@@ -113,2 +116,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import findMediaOperator from "../../utils/findMediaOperator.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -26,3 +26,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -43,2 +43,3 @@ | ||
const fixOperatorIndices = [] | ||
/** @type {((index: number) => void) | null} */ | ||
@@ -108,2 +109,3 @@ const fix = context.fix ? (index) => fixOperatorIndices.push(index) : null | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import findMediaOperator from "../../utils/findMediaOperator.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -26,3 +26,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -43,2 +43,3 @@ | ||
const fixOperatorIndices = [] | ||
/** @type {((index: number) => void) | null} */ | ||
@@ -108,2 +109,3 @@ const fix = context.fix ? (index) => fixOperatorIndices.push(index) : null | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import mediaQueryListCommaWhitespaceChecker from "../../utils/mediaQueryListCommaWhitespaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,3 +27,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -52,13 +52,16 @@ | ||
allowTrailingComments: primary.startsWith(`always`), | ||
fix: context.fix ? (atRule, index) => { | ||
const paramCommaIndex = index - atRuleParamIndex(atRule) | ||
fix: context.fix | ||
? (atRule, index) => { | ||
const paramCommaIndex = index - atRuleParamIndex(atRule) | ||
fixData = fixData || new Map() | ||
const commaIndices = fixData.get(atRule) || [] | ||
fixData = fixData || (new Map) | ||
commaIndices.push(paramCommaIndex) | ||
fixData.set(atRule, commaIndices) | ||
const commaIndices = fixData.get(atRule) || [] | ||
return true | ||
} : null, | ||
commaIndices.push(paramCommaIndex) | ||
fixData.set(atRule, commaIndices) | ||
return true | ||
} | ||
: null, | ||
}) | ||
@@ -75,3 +78,3 @@ | ||
if (primary.startsWith(`always`)) { | ||
params = /^\s*\n/.test(afterComma) ? beforeComma + afterComma.replace(/^[^\S\r\n]*/, ``) : beforeComma + context.newline + afterComma | ||
params = (/^\s*\n/).test(afterComma) ? beforeComma + afterComma.replace(/^[^\S\r\n]*/, ``) : beforeComma + context.newline + afterComma | ||
} else if (primary.startsWith(`never`)) { | ||
@@ -95,2 +98,3 @@ params = beforeComma + afterComma.replace(/^\s*/, ``) | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import mediaQueryListCommaWhitespaceChecker from "../../utils/mediaQueryListCommaWhitespaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -25,3 +25,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary) => { | ||
function rule (primary) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -51,2 +51,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import mediaQueryListCommaWhitespaceChecker from "../../utils/mediaQueryListCommaWhitespaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -28,3 +28,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -50,13 +50,16 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (atRule, index) => { | ||
const paramCommaIndex = index - atRuleParamIndex(atRule) | ||
fix: context.fix | ||
? (atRule, index) => { | ||
const paramCommaIndex = index - atRuleParamIndex(atRule) | ||
fixData = fixData || new Map() | ||
const commaIndices = fixData.get(atRule) || [] | ||
fixData = fixData || (new Map) | ||
commaIndices.push(paramCommaIndex) | ||
fixData.set(atRule, commaIndices) | ||
const commaIndices = fixData.get(atRule) || [] | ||
return true | ||
} : null, | ||
commaIndices.push(paramCommaIndex) | ||
fixData.set(atRule, commaIndices) | ||
return true | ||
} | ||
: null, | ||
}) | ||
@@ -92,2 +95,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import mediaQueryListCommaWhitespaceChecker from "../../utils/mediaQueryListCommaWhitespaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -28,3 +28,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -50,13 +50,16 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (atRule, index) => { | ||
const paramCommaIndex = index - atRuleParamIndex(atRule) | ||
fix: context.fix | ||
? (atRule, index) => { | ||
const paramCommaIndex = index - atRuleParamIndex(atRule) | ||
fixData = fixData || new Map() | ||
const commaIndices = fixData.get(atRule) || [] | ||
fixData = fixData || (new Map) | ||
commaIndices.push(paramCommaIndex) | ||
fixData.set(atRule, commaIndices) | ||
const commaIndices = fixData.get(atRule) || [] | ||
return true | ||
} : null, | ||
commaIndices.push(paramCommaIndex) | ||
fixData.set(atRule, commaIndices) | ||
return true | ||
} | ||
: null, | ||
}) | ||
@@ -92,2 +95,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import valueParser from "postcss-value-parser" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import getDeclarationValue from "../../utils/getDeclarationValue.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import setDeclarationValue from "../../utils/setDeclarationValue.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import { isBoolean, isNumber } from "../../utils/validateTypes.js" | ||
@@ -27,115 +27,128 @@ | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions = {}, context) => (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ actual: primary }, | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
gap: [isNumber, (value) => value > 1], | ||
alignQuotes: [isBoolean], | ||
function rule (primary, secondaryOptions = {}, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ actual: primary }, | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
gap: [isNumber, (value) => value > 1], | ||
alignQuotes: [isBoolean], | ||
}, | ||
optional: true, | ||
}, | ||
optional: true, | ||
}, | ||
) | ||
) | ||
if (!validOptions) { return } | ||
if (!validOptions) { return } | ||
const gap = secondaryOptions.gap ?? 1 | ||
const alignQuotes = secondaryOptions.alignQuotes ?? false | ||
const gap = secondaryOptions.gap ?? 1 | ||
const alignQuotes = secondaryOptions.alignQuotes ?? false | ||
const referenceGap = ` `.repeat(gap) | ||
const referenceGap = ` `.repeat(gap) | ||
root.walkDecls(`grid-template-areas`, (declaration) => { | ||
const declarationValue = getDeclarationValue(declaration) | ||
const parsedValue = valueParser(declarationValue) | ||
const isMultilineDeclaration = declarationValue.includes(`\n`) | ||
root.walkDecls(`grid-template-areas`, (declaration) => { | ||
const declarationValue = getDeclarationValue(declaration) | ||
const parsedValue = valueParser(declarationValue) | ||
const isMultilineDeclaration = declarationValue.includes(`\n`) | ||
const gridRows = parsedValue.nodes.filter((node) => node.type === `string`) | ||
const gridRows = parsedValue.nodes.filter((node) => node.type === `string`) | ||
// To compare with the formatted value to determine if there is an error | ||
const originalRows = gridRows.map(({ value }) => value).filter(Boolean) | ||
// The ones to operate with | ||
const rows = gridRows | ||
.map(({ value }) => value.trim().replaceAll(/\s+/g, ` `)) | ||
.filter(Boolean) | ||
// To compare with the formatted value to determine if there is an error | ||
const originalRows = gridRows.map(({ value }) => value).filter(Boolean) | ||
// The ones to operate with | ||
const rows = gridRows | ||
.map(({ value }) => value.trim().replaceAll(/\s+/g, ` `)) | ||
.filter(Boolean) | ||
let maxCellsCount = 0 | ||
const table = rows.reduce((acc, row) => { | ||
const cells = row.split(` `) | ||
maxCellsCount = Math.max(maxCellsCount, cells.length) | ||
acc.push(row.split(` `)) | ||
return acc | ||
}, []) | ||
let maxCellsCount = 0 | ||
const table = rows.reduce((acc, row) => { | ||
const cells = row.split(` `) | ||
const maxLengths = new Array(maxCellsCount).fill(``).reduce((acc, part, index) => { | ||
const parts = table.map((row) => row[index]?.length ?? 0) | ||
acc.push(Math.max(part.length, ...parts)) | ||
return acc | ||
}, []) | ||
maxCellsCount = Math.max(maxCellsCount, cells.length) | ||
acc.push(row.split(` `)) | ||
let maxRowLength = 0 | ||
let formatted = table.map((row) => { | ||
const formattedRow = row | ||
.map((cell, index) => isMultilineDeclaration ? cell.padEnd(maxLengths[index], ` `) : cell) | ||
.join(referenceGap) | ||
maxRowLength = Math.max(maxRowLength, formattedRow.length) | ||
return alignQuotes ? formattedRow : formattedRow.trimEnd() | ||
}) | ||
return acc | ||
}, []) | ||
if (alignQuotes && isMultilineDeclaration) { | ||
formatted = formatted.map((row) => { | ||
if (row.length === maxRowLength) { return row } | ||
const cleanRowValue = row.trimEnd() | ||
return `${cleanRowValue}${` `.repeat(maxRowLength - cleanRowValue.length)}` | ||
const maxLengths = new Array(maxCellsCount).fill(``).reduce((acc, part, index) => { | ||
const parts = table.map((row) => row[index]?.length ?? 0) | ||
acc.push(Math.max(part.length, ...parts)) | ||
return acc | ||
}, []) | ||
let maxRowLength = 0 | ||
let formatted = table.map((row) => { | ||
const formattedRow = row | ||
.map((cell, index) => isMultilineDeclaration ? cell.padEnd(maxLengths[index], ` `) : cell) | ||
.join(referenceGap) | ||
maxRowLength = Math.max(maxRowLength, formattedRow.length) | ||
return alignQuotes ? formattedRow : formattedRow.trimEnd() | ||
}) | ||
} | ||
const isValid = originalRows.every((row, index) => row === formatted[index]) | ||
if (isValid) { return } | ||
if (alignQuotes && isMultilineDeclaration) { | ||
formatted = formatted.map((row) => { | ||
if (row.length === maxRowLength) { return row } | ||
if (context.fix) { | ||
const formattedValue = parsedValue.nodes.reduce((acc, node) => { | ||
if (node.type === `string`) { | ||
acc.push(`${node.quote}${formatted.shift()}${node.quote}`) | ||
const cleanRowValue = row.trimEnd() | ||
return `${cleanRowValue}${` `.repeat(maxRowLength - cleanRowValue.length)}` | ||
}) | ||
} | ||
const isValid = originalRows.every((row, index) => row === formatted[index]) | ||
if (isValid) { return } | ||
if (context.fix) { | ||
const formattedValue = parsedValue.nodes.reduce((acc, node) => { | ||
if (node.type === `string`) { | ||
acc.push(`${node.quote}${formatted.shift()}${node.quote}`) | ||
return acc | ||
} | ||
if (node.type === `comment`) { | ||
acc.push(`/*${node.value}*/`) | ||
return acc | ||
} | ||
acc.push(`${node.before ?? ``}${node.value}${node.after ?? ``}`) | ||
return acc | ||
} | ||
if (node.type === `comment`) { | ||
acc.push(`/*${node.value}*/`) | ||
return acc | ||
} | ||
}, []).join(``) | ||
acc.push(`${node.before ?? ``}${node.value}${node.after ?? ``}`) | ||
return acc | ||
}, []).join(``) | ||
setDeclarationValue(declaration, formattedValue) | ||
setDeclarationValue(declaration, formattedValue) | ||
return | ||
} | ||
return | ||
} | ||
const extraStartLines = declaration.raws.between.match(/[\r\n?|\n]*/g) | ||
?.reduce((acc, newLineBlock) => acc + newLineBlock.length, 0) | ||
const extraStartLines = declaration.raws.between.match(/[\r\n?|\n]*/g) | ||
?.reduce((acc, newLineBlock) => acc + newLineBlock.length, 0) | ||
/* eslint-disable operator-linebreak */ | ||
const extraStartColumns = extraStartLines === 0 | ||
? declarationValueIndex(declaration) + declaration.source.start.column | ||
: declaration.raws.between.match(/[^\r\n?|\n]+$/)?.[0].length + 1 ?? 0 | ||
/* eslint-enable operator-linebreak */ | ||
const extraStartColumns = extraStartLines === 0 | ||
? declarationValueIndex(declaration) + declaration.source.start.column | ||
: declaration.raws.between.match(/[^\r\n?|\n]+$/)?.[0].length + 1 || 0 | ||
report({ | ||
message: messages.expected(), | ||
node: declaration, | ||
start: { | ||
line: extraStartLines + declaration.source.start.line, | ||
column: extraStartColumns, | ||
}, | ||
end: { | ||
line: declaration.source.end.line, | ||
column: declaration.source.end.column, | ||
}, | ||
result, | ||
ruleName, | ||
report({ | ||
message: messages.expected(), | ||
node: declaration, | ||
start: { | ||
line: extraStartLines + declaration.source.start.line, | ||
column: extraStartColumns, | ||
}, | ||
end: { | ||
line: declaration.source.end.line, | ||
column: declaration.source.end.column, | ||
}, | ||
result, | ||
ruleName, | ||
}) | ||
}) | ||
}) | ||
} | ||
} | ||
@@ -146,2 +159,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
@@ -10,2 +11,3 @@ import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
export const ruleName = addNamespace(shortName) | ||
export const noEmptyFirstLineTest = /^\s*[\r\n]/ | ||
@@ -23,37 +25,39 @@ | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { actual: primary }) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { actual: primary }) | ||
// @ts-expect-error -- TS2339: Property 'inline' does not exist on type 'Source'. Property 'lang' does not exist on type 'Source'. | ||
if (!validOptions || root.source.inline || root.source.lang === `object-literal`) { | ||
return | ||
} | ||
// @ts-expect-error -- TS2339: Property 'inline' does not exist on type 'Source'. Property 'lang' does not exist on type 'Source'. | ||
if (!validOptions || root.source.inline || root.source.lang === `object-literal`) { | ||
return | ||
} | ||
const rootString = context.fix ? root.toString() : (root.source && root.source.input.css) || `` | ||
const rootString = context.fix ? root.toString() : (root.source && root.source.input.css) || `` | ||
if (!rootString.trim()) { | ||
return | ||
} | ||
if (!rootString.trim()) { | ||
return | ||
} | ||
if (noEmptyFirstLineTest.test(rootString)) { | ||
if (context.fix) { | ||
if (root.first === null) { | ||
throw new Error(`The root node must have the first node.`) | ||
} | ||
if (noEmptyFirstLineTest.test(rootString)) { | ||
if (context.fix) { | ||
if (root.first === null) { | ||
throw new Error(`The root node must have the first node.`) | ||
} | ||
if (root.first.raws.before === null) { | ||
throw new Error(`The first node must have spaces before.`) | ||
if (root.first.raws.before === null) { | ||
throw new Error(`The first node must have spaces before.`) | ||
} | ||
root.first.raws.before = root.first.raws.before.trimStart() | ||
return | ||
} | ||
root.first.raws.before = root.first.raws.before.trimStart() | ||
return | ||
report({ | ||
message: messages.rejected, | ||
node: root, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
report({ | ||
message: messages.rejected, | ||
node: root, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
@@ -65,2 +69,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import styleSearch from "style-search" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isOnlyWhitespace from "../../utils/isOnlyWhitespace.js" | ||
@@ -8,4 +10,2 @@ import isStandardSyntaxComment from "../../utils/isStandardSyntaxComment.js" | ||
import { isAtRule, isComment, isDeclaration, isRule } from "../../utils/typeGuards.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -69,221 +69,223 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
}, | ||
{ | ||
optional: true, | ||
actual: secondaryOptions, | ||
possible: { | ||
ignore: [`empty-lines`], | ||
function rule (primary, secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
}, | ||
}, | ||
) | ||
{ | ||
optional: true, | ||
actual: secondaryOptions, | ||
possible: { | ||
ignore: [`empty-lines`], | ||
}, | ||
}, | ||
) | ||
if (!validOptions) { | ||
return | ||
} | ||
if (!validOptions) { | ||
return | ||
} | ||
const ignoreEmptyLines = optionsMatches(secondaryOptions, `ignore`, `empty-lines`) | ||
const ignoreEmptyLines = optionsMatches(secondaryOptions, `ignore`, `empty-lines`) | ||
if (context.fix) { | ||
fix(root) | ||
} | ||
if (context.fix) { | ||
fix(root) | ||
} | ||
const rootString = context.fix ? root.toString() : (root.source && root.source.input.css) || `` | ||
const rootString = context.fix ? root.toString() : (root.source && root.source.input.css) || `` | ||
/** | ||
* @param {number} index | ||
*/ | ||
const reportFromIndex = (index) => { | ||
report({ | ||
message: messages.rejected, | ||
node: root, | ||
index, | ||
result, | ||
ruleName, | ||
/** | ||
* @param {number} index | ||
*/ | ||
function reportFromIndex (index) { | ||
report({ | ||
message: messages.rejected, | ||
node: root, | ||
index, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
eachEolWhitespace(rootString, reportFromIndex, true) | ||
const errorIndex = findErrorStartIndex(rootString.length, rootString, { | ||
ignoreEmptyLines, | ||
isRootFirst: true, | ||
}) | ||
} | ||
eachEolWhitespace(rootString, reportFromIndex, true) | ||
if (errorIndex > -1) { | ||
reportFromIndex(errorIndex) | ||
} | ||
const errorIndex = findErrorStartIndex(rootString.length, rootString, { | ||
ignoreEmptyLines, | ||
isRootFirst: true, | ||
}) | ||
/** | ||
* Iterate each whitespace at the end of each line of the given string. | ||
* @param {string} string - the source code string | ||
* @param {(index: number) => void} callback - callback the whitespace index at the end of each line. | ||
* @param {boolean} isRootFirst - set `true` if the given string is the first token of the root. | ||
* @returns {void} | ||
*/ | ||
function eachEolWhitespace (string, callback, isRootFirst) { | ||
styleSearch( | ||
{ | ||
source: string, | ||
target: [`\n`, `\r`], | ||
comments: `check`, | ||
}, | ||
(match) => { | ||
const index = findErrorStartIndex(match.startIndex, string, { | ||
ignoreEmptyLines, | ||
isRootFirst, | ||
}) | ||
if (errorIndex > -1) { | ||
reportFromIndex(errorIndex) | ||
} | ||
if (index > -1) { | ||
callback(index) | ||
} | ||
}, | ||
) | ||
} | ||
/** | ||
* Iterate each whitespace at the end of each line of the given string. | ||
* @param {string} string - the source code string | ||
* @param {(index: number) => void} callback - callback the whitespace index at the end of each line. | ||
* @param {boolean} isRootFirst - set `true` if the given string is the first token of the root. | ||
* @returns {void} | ||
*/ | ||
function eachEolWhitespace (string, callback, isRootFirst) { | ||
styleSearch( | ||
{ | ||
source: string, | ||
target: [`\n`, `\r`], | ||
comments: `check`, | ||
}, | ||
(match) => { | ||
const index = findErrorStartIndex(match.startIndex, string, { | ||
ignoreEmptyLines, | ||
/** | ||
* @param {import('postcss').Root} rootNode | ||
*/ | ||
function fix (rootNode) { | ||
let isRootFirst = true | ||
rootNode.walk((node) => { | ||
fixText( | ||
node.raws.before, | ||
(fixed) => { | ||
node.raws.before = fixed | ||
}, | ||
isRootFirst, | ||
}) | ||
) | ||
isRootFirst = false | ||
if (index > -1) { | ||
callback(index) | ||
} | ||
}, | ||
) | ||
} | ||
if (isAtRule(node)) { | ||
fixText(node.raws.afterName, (fixed) => { | ||
node.raws.afterName = fixed | ||
}) | ||
/** | ||
* @param {import('postcss').Root} rootNode | ||
*/ | ||
function fix (rootNode) { | ||
let isRootFirst = true | ||
const rawsParams = node.raws.params | ||
rootNode.walk((node) => { | ||
fixText( | ||
node.raws.before, | ||
(fixed) => { | ||
node.raws.before = fixed | ||
}, | ||
isRootFirst, | ||
) | ||
isRootFirst = false | ||
if (rawsParams) { | ||
fixText(rawsParams.raw, (fixed) => { | ||
rawsParams.raw = fixed | ||
}) | ||
} else { | ||
fixText(node.params, (fixed) => { | ||
node.params = fixed | ||
}) | ||
} | ||
} | ||
if (isAtRule(node)) { | ||
fixText(node.raws.afterName, (fixed) => { | ||
node.raws.afterName = fixed | ||
}) | ||
if (isRule(node)) { | ||
const rawsSelector = node.raws.selector | ||
const rawsParams = node.raws.params | ||
if (rawsSelector) { | ||
fixText(rawsSelector.raw, (fixed) => { | ||
rawsSelector.raw = fixed | ||
}) | ||
} else { | ||
fixText(node.selector, (fixed) => { | ||
node.selector = fixed | ||
}) | ||
} | ||
} | ||
if (rawsParams) { | ||
fixText(rawsParams.raw, (fixed) => { | ||
rawsParams.raw = fixed | ||
if (isAtRule(node) || isRule(node) || isDeclaration(node)) { | ||
fixText(node.raws.between, (fixed) => { | ||
node.raws.between = fixed | ||
}) | ||
} else { | ||
fixText(node.params, (fixed) => { | ||
node.params = fixed | ||
}) | ||
} | ||
} | ||
if (isRule(node)) { | ||
const rawsSelector = node.raws.selector | ||
if (isDeclaration(node)) { | ||
const rawsValue = node.raws.value | ||
if (rawsSelector) { | ||
fixText(rawsSelector.raw, (fixed) => { | ||
rawsSelector.raw = fixed | ||
}) | ||
} else { | ||
fixText(node.selector, (fixed) => { | ||
node.selector = fixed | ||
}) | ||
if (rawsValue) { | ||
fixText(rawsValue.raw, (fixed) => { | ||
rawsValue.raw = fixed | ||
}) | ||
} else { | ||
fixText(node.value, (fixed) => { | ||
node.value = fixed | ||
}) | ||
} | ||
} | ||
} | ||
if (isAtRule(node) || isRule(node) || isDeclaration(node)) { | ||
fixText(node.raws.between, (fixed) => { | ||
node.raws.between = fixed | ||
}) | ||
} | ||
if (isComment(node)) { | ||
fixText(node.raws.left, (fixed) => { | ||
node.raws.left = fixed | ||
}) | ||
if (isDeclaration(node)) { | ||
const rawsValue = node.raws.value | ||
if (!isStandardSyntaxComment(node)) { | ||
node.raws.right = node.raws.right && fixString(node.raws.right) | ||
} else { | ||
fixText(node.raws.right, (fixed) => { | ||
node.raws.right = fixed | ||
}) | ||
} | ||
if (rawsValue) { | ||
fixText(rawsValue.raw, (fixed) => { | ||
rawsValue.raw = fixed | ||
fixText(node.text, (fixed) => { | ||
node.text = fixed | ||
}) | ||
} else { | ||
fixText(node.value, (fixed) => { | ||
node.value = fixed | ||
}) | ||
} | ||
} | ||
if (isComment(node)) { | ||
fixText(node.raws.left, (fixed) => { | ||
node.raws.left = fixed | ||
}) | ||
if (!isStandardSyntaxComment(node)) { | ||
node.raws.right = node.raws.right && fixString(node.raws.right) | ||
} else { | ||
fixText(node.raws.right, (fixed) => { | ||
node.raws.right = fixed | ||
if (isAtRule(node) || isRule(node)) { | ||
fixText(node.raws.after, (fixed) => { | ||
node.raws.after = fixed | ||
}) | ||
} | ||
}) | ||
fixText(node.text, (fixed) => { | ||
node.text = fixed | ||
}) | ||
} | ||
fixText( | ||
rootNode.raws.after, | ||
(fixed) => { | ||
rootNode.raws.after = fixed | ||
}, | ||
isRootFirst, | ||
) | ||
if (isAtRule(node) || isRule(node)) { | ||
fixText(node.raws.after, (fixed) => { | ||
node.raws.after = fixed | ||
}) | ||
} | ||
}) | ||
if (typeof rootNode.raws.after === `string`) { | ||
const lastEOL = Math.max( | ||
rootNode.raws.after.lastIndexOf(`\n`), | ||
rootNode.raws.after.lastIndexOf(`\r`), | ||
) | ||
fixText( | ||
rootNode.raws.after, | ||
(fixed) => { | ||
rootNode.raws.after = fixed | ||
}, | ||
isRootFirst, | ||
) | ||
if (typeof rootNode.raws.after === `string`) { | ||
const lastEOL = Math.max( | ||
rootNode.raws.after.lastIndexOf(`\n`), | ||
rootNode.raws.after.lastIndexOf(`\r`), | ||
) | ||
if (lastEOL !== rootNode.raws.after.length - 1) { | ||
rootNode.raws.after = rootNode.raws.after.slice(0, lastEOL + 1) + fixString(rootNode.raws.after.slice(lastEOL + 1)) | ||
if (lastEOL !== rootNode.raws.after.length - 1) { | ||
rootNode.raws.after = rootNode.raws.after.slice(0, lastEOL + 1) + fixString(rootNode.raws.after.slice(lastEOL + 1)) | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* @param {string | undefined} value | ||
* @param {(text: string) => void} fixFn | ||
* @param {boolean} isRootFirst | ||
*/ | ||
function fixText (value, fixFn, isRootFirst = false) { | ||
if (!value) { | ||
return | ||
} | ||
/** | ||
* @param {string | undefined} value | ||
* @param {(text: string) => void} fixFn | ||
* @param {boolean} isRootFirst | ||
*/ | ||
function fixText (value, fixFn, isRootFirst = false) { | ||
if (!value) { | ||
return | ||
} | ||
let fixed = `` | ||
let lastIndex = 0 | ||
let fixed = `` | ||
let lastIndex = 0 | ||
eachEolWhitespace( | ||
value, | ||
(index) => { | ||
const newlineIndex = index + 1 | ||
eachEolWhitespace( | ||
value, | ||
(index) => { | ||
const newlineIndex = index + 1 | ||
fixed += fixString(value.slice(lastIndex, newlineIndex)) | ||
lastIndex = newlineIndex | ||
}, | ||
isRootFirst, | ||
) | ||
fixed += fixString(value.slice(lastIndex, newlineIndex)) | ||
lastIndex = newlineIndex | ||
}, | ||
isRootFirst, | ||
) | ||
if (lastIndex) { | ||
fixed += value.slice(lastIndex) | ||
fixFn(fixed) | ||
if (lastIndex) { | ||
fixed += value.slice(lastIndex) | ||
fixFn(fixed) | ||
} | ||
} | ||
@@ -296,2 +298,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import styleSearch from "style-search" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import { isAtRule } from "../../utils/typeGuards.js" | ||
import isStandardSyntaxAtRule from "../../utils/isStandardSyntaxAtRule.js" | ||
import isStandardSyntaxRule from "../../utils/isStandardSyntaxRule.js" | ||
import { isAtRule } from "../../utils/typeGuards.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -37,7 +37,7 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
if (!root.source) {throw new Error(`The root node must have a source`)} | ||
if (!root.source) { throw new Error(`The root node must have a source`) } | ||
if (!node.source) {throw new Error(`The node must have a source`)} | ||
if (!node.source) { throw new Error(`The node must have a source`) } | ||
if (!node.source.start) {throw new Error(`The source must have a start position`)} | ||
if (!node.source.start) { throw new Error(`The source must have a start position`) } | ||
@@ -69,58 +69,19 @@ const string = root.source.input.css | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { actual: primary }) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { actual: primary }) | ||
if (!validOptions) { | ||
return | ||
} | ||
if (root.raws.after && root.raws.after.trim().length !== 0) { | ||
const rawAfterRoot = root.raws.after | ||
/** @type {number[]} */ | ||
const fixSemiIndices = [] | ||
styleSearch({ source: rawAfterRoot, target: `;` }, (match) => { | ||
if (context.fix) { | ||
fixSemiIndices.push(match.startIndex) | ||
return | ||
} | ||
if (!root.source) {throw new Error(`The root node must have a source`)} | ||
complain(root.source.input.css.length - rawAfterRoot.length + match.startIndex) | ||
}) | ||
// fix | ||
if (fixSemiIndices.length) { | ||
root.raws.after = removeIndices(rawAfterRoot, fixSemiIndices) | ||
} | ||
} | ||
root.walk((node) => { | ||
if (isAtRule(node) && !isStandardSyntaxAtRule(node)) { | ||
if (!validOptions) { | ||
return | ||
} | ||
if (node.type === `rule` && !isStandardSyntaxRule(node)) { | ||
return | ||
} | ||
if (root.raws.after && root.raws.after.trim().length !== 0) { | ||
const rawAfterRoot = root.raws.after | ||
if (node.raws.before && node.raws.before.trim().length !== 0) { | ||
const rawBeforeNode = node.raws.before | ||
const allowedSemi = 0 | ||
const rawBeforeIndexStart = 0 | ||
/** @type {number[]} */ | ||
const fixSemiIndices = [] | ||
styleSearch({ source: rawBeforeNode, target: `;` }, (match, count) => { | ||
if (count === allowedSemi) { | ||
return | ||
} | ||
styleSearch({ source: rawAfterRoot, target: `;` }, (match) => { | ||
if (context.fix) { | ||
fixSemiIndices.push(match.startIndex - rawBeforeIndexStart) | ||
fixSemiIndices.push(match.startIndex) | ||
@@ -130,3 +91,5 @@ return | ||
complain(getOffsetByNode(node) - rawBeforeNode.length + match.startIndex) | ||
if (!root.source) { throw new Error(`The root node must have a source`) } | ||
complain(root.source.input.css.length - rawAfterRoot.length + match.startIndex) | ||
}) | ||
@@ -136,93 +99,132 @@ | ||
if (fixSemiIndices.length) { | ||
node.raws.before = removeIndices(rawBeforeNode, fixSemiIndices) | ||
root.raws.after = removeIndices(rawAfterRoot, fixSemiIndices) | ||
} | ||
} | ||
if (typeof node.raws.after === `string` && node.raws.after.trim().length !== 0) { | ||
const rawAfterNode = node.raws.after | ||
root.walk((node) => { | ||
if (isAtRule(node) && !isStandardSyntaxAtRule(node)) { | ||
return | ||
} | ||
/** | ||
* If the last child is a Less mixin followed by more than one semicolon, | ||
* node.raws.after will be populated with that semicolon. | ||
* Since we ignore Less mixins, exit here | ||
*/ | ||
if (`last` in node && node.last && node.last.type === `atrule` && !isStandardSyntaxAtRule(node.last)) { | ||
if (node.type === `rule` && !isStandardSyntaxRule(node)) { | ||
return | ||
} | ||
/** @type {number[]} */ | ||
const fixSemiIndices = [] | ||
if (node.raws.before && node.raws.before.trim().length !== 0) { | ||
const rawBeforeNode = node.raws.before | ||
const allowedSemi = 0 | ||
styleSearch({ source: rawAfterNode, target: `;` }, (match) => { | ||
if (context.fix) { | ||
fixSemiIndices.push(match.startIndex) | ||
const rawBeforeIndexStart = 0 | ||
return | ||
} | ||
/** @type {number[]} */ | ||
const fixSemiIndices = [] | ||
const index = getOffsetByNode(node) + node.toString().length - 1 - rawAfterNode.length + match.startIndex | ||
styleSearch({ source: rawBeforeNode, target: `;` }, (match, count) => { | ||
if (count === allowedSemi) { | ||
return | ||
} | ||
complain(index) | ||
}) | ||
if (context.fix) { | ||
fixSemiIndices.push(match.startIndex - rawBeforeIndexStart) | ||
// fix | ||
if (fixSemiIndices.length) { | ||
node.raws.after = removeIndices(rawAfterNode, fixSemiIndices) | ||
return | ||
} | ||
complain(getOffsetByNode(node) - rawBeforeNode.length + match.startIndex) | ||
}) | ||
// fix | ||
if (fixSemiIndices.length) { | ||
node.raws.before = removeIndices(rawBeforeNode, fixSemiIndices) | ||
} | ||
} | ||
} | ||
if (typeof node.raws.ownSemicolon === `string`) { | ||
const rawOwnSemicolon = node.raws.ownSemicolon | ||
const allowedSemi = 0 | ||
if (typeof node.raws.after === `string` && node.raws.after.trim().length !== 0) { | ||
const rawAfterNode = node.raws.after | ||
/** @type {number[]} */ | ||
const fixSemiIndices = [] | ||
styleSearch({ source: rawOwnSemicolon, target: `;` }, (match, count) => { | ||
if (count === allowedSemi) { | ||
/** | ||
* If the last child is a Less mixin followed by more than one semicolon, | ||
* node.raws.after will be populated with that semicolon. | ||
* Since we ignore Less mixins, exit here | ||
*/ | ||
if (`last` in node && node.last && node.last.type === `atrule` && !isStandardSyntaxAtRule(node.last)) { | ||
return | ||
} | ||
if (context.fix) { | ||
fixSemiIndices.push(match.startIndex) | ||
/** @type {number[]} */ | ||
const fixSemiIndices = [] | ||
return | ||
styleSearch({ source: rawAfterNode, target: `;` }, (match) => { | ||
if (context.fix) { | ||
fixSemiIndices.push(match.startIndex) | ||
return | ||
} | ||
const index = getOffsetByNode(node) + node.toString().length - 1 - rawAfterNode.length + match.startIndex | ||
complain(index) | ||
}) | ||
// fix | ||
if (fixSemiIndices.length) { | ||
node.raws.after = removeIndices(rawAfterNode, fixSemiIndices) | ||
} | ||
} | ||
const index = getOffsetByNode(node) + node.toString().length - rawOwnSemicolon.length + match.startIndex | ||
if (typeof node.raws.ownSemicolon === `string`) { | ||
const rawOwnSemicolon = node.raws.ownSemicolon | ||
const allowedSemi = 0 | ||
complain(index) | ||
}) | ||
/** @type {number[]} */ | ||
const fixSemiIndices = [] | ||
// fix | ||
if (fixSemiIndices.length) { | ||
node.raws.ownSemicolon = removeIndices(rawOwnSemicolon, fixSemiIndices) | ||
styleSearch({ source: rawOwnSemicolon, target: `;` }, (match, count) => { | ||
if (count === allowedSemi) { | ||
return | ||
} | ||
if (context.fix) { | ||
fixSemiIndices.push(match.startIndex) | ||
return | ||
} | ||
const index = getOffsetByNode(node) + node.toString().length - rawOwnSemicolon.length + match.startIndex | ||
complain(index) | ||
}) | ||
// fix | ||
if (fixSemiIndices.length) { | ||
node.raws.ownSemicolon = removeIndices(rawOwnSemicolon, fixSemiIndices) | ||
} | ||
} | ||
}) | ||
/** | ||
* @param {number} index | ||
*/ | ||
function complain (index) { | ||
report({ | ||
message: messages.rejected, | ||
node: root, | ||
index, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
}) | ||
/** | ||
* @param {number} index | ||
*/ | ||
function complain (index) { | ||
report({ | ||
message: messages.rejected, | ||
node: root, | ||
index, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
/** | ||
* @param {string} str | ||
* @param {number[]} indices | ||
* @returns {string} | ||
*/ | ||
function removeIndices (str, indices) { | ||
for (const index of indices.reverse()) { | ||
str = str.slice(0, index) + str.slice(index + 1) | ||
} | ||
/** | ||
* @param {string} str | ||
* @param {number[]} indices | ||
* @returns {string} | ||
*/ | ||
function removeIndices (str, indices) { | ||
for (const index of indices.reverse()) { | ||
str = str.slice(0, index) + str.slice(index + 1) | ||
return str | ||
} | ||
return str | ||
} | ||
@@ -234,2 +236,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
@@ -21,38 +22,40 @@ import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { actual: primary }) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { actual: primary }) | ||
if (!validOptions) { | ||
return | ||
} | ||
if (!validOptions) { | ||
return | ||
} | ||
if (root.source === null) { | ||
throw new Error(`The root node must have a source property`) | ||
} | ||
if (root.source === null) { | ||
throw new Error(`The root node must have a source property`) | ||
} | ||
// @ts-expect-error -- TS2339: Property 'inline' does not exist on type 'Source'. | ||
if (root.source.inline || root.source.lang === `object-literal`) { | ||
return | ||
} | ||
// @ts-expect-error -- TS2339: Property 'inline' does not exist on type 'Source'. | ||
if (root.source.inline || root.source.lang === `object-literal`) { | ||
return | ||
} | ||
const rootString = context.fix ? root.toString() : root.source.input.css | ||
const rootString = context.fix ? root.toString() : root.source.input.css | ||
if (!rootString.trim() || rootString.endsWith(`\n`)) { | ||
return | ||
} | ||
if (!rootString.trim() || rootString.endsWith(`\n`)) { | ||
return | ||
} | ||
// Fix | ||
if (context.fix) { | ||
root.raws.after = context.newline | ||
// Fix | ||
if (context.fix) { | ||
root.raws.after = context.newline | ||
return | ||
return | ||
} | ||
report({ | ||
message: messages.rejected, | ||
node: root, | ||
index: rootString.length - 1, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
report({ | ||
message: messages.rejected, | ||
node: root, | ||
index: rootString.length - 1, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
@@ -63,2 +66,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import valueParser from "postcss-value-parser" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import { isAtRule } from "../../utils/typeGuards.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,145 +27,148 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
root.walkAtRules((atRule) => { | ||
if (atRule.name.toLowerCase() === `import`) { | ||
if (!validOptions) { | ||
return | ||
} | ||
check(atRule, atRule.params) | ||
}) | ||
root.walkAtRules((atRule) => { | ||
if (atRule.name.toLowerCase() === `import`) { | ||
return | ||
} | ||
root.walkDecls((decl) => check(decl, decl.value)) | ||
check(atRule, atRule.params) | ||
}) | ||
/** | ||
* @param {import('postcss').AtRule | import('postcss').Declaration} node | ||
* @param {string} value | ||
*/ | ||
function check (node, value) { | ||
/** @type {Array<{ startIndex: number, endIndex: number }>} */ | ||
const neverFixPositions = [] | ||
/** @type {Array<{ index: number }>} */ | ||
const alwaysFixPositions = [] | ||
root.walkDecls((decl) => check(decl, decl.value)) | ||
// Get out quickly if there are no periods | ||
if (!value.includes(`.`)) { | ||
return | ||
} | ||
/** | ||
* @param {import('postcss').AtRule | import('postcss').Declaration} node | ||
* @param {string} value | ||
*/ | ||
function check (node, value) { | ||
/** @type {Array<{ startIndex: number, endIndex: number }>} */ | ||
const neverFixPositions = [] | ||
valueParser(value).walk((valueNode) => { | ||
// Ignore `url` function | ||
if (valueNode.type === `function` && valueNode.value.toLowerCase() === `url`) { | ||
return false | ||
} | ||
/** @type {Array<{ index: number }>} */ | ||
const alwaysFixPositions = [] | ||
// Ignore strings, comments, etc | ||
if (valueNode.type !== `word`) { | ||
// Get out quickly if there are no periods | ||
if (!value.includes(`.`)) { | ||
return | ||
} | ||
// Check leading zero | ||
if (primary === `always`) { | ||
const match = /(?:\D|^)(\.\d+)/.exec(valueNode.value) | ||
valueParser(value).walk((valueNode) => { | ||
// Ignore `url` function | ||
if (valueNode.type === `function` && valueNode.value.toLowerCase() === `url`) { | ||
return false | ||
} | ||
if (match === null || match[0] === null || match[1] === null) { | ||
// Ignore strings, comments, etc | ||
if (valueNode.type !== `word`) { | ||
return | ||
} | ||
// The regexp above consists of 2 capturing groups (or capturing parentheses). | ||
// We need the index of the second group. This makes sanse when we have "-.5" as an input | ||
// for regex. And we need the index of ".5". | ||
const capturingGroupIndex = match[0].length - match[1].length | ||
// Check leading zero | ||
if (primary === `always`) { | ||
const match = (/(?:\D|^)(\.\d+)/).exec(valueNode.value) | ||
const index = valueNode.sourceIndex + match.index + capturingGroupIndex | ||
if (match === null || match[0] === null || match[1] === null) { | ||
return | ||
} | ||
if (context.fix) { | ||
alwaysFixPositions.unshift({ | ||
index, | ||
}) | ||
// The regexp above consists of 2 capturing groups (or capturing parentheses). | ||
// We need the index of the second group. This makes sanse when we have "-.5" as an input | ||
// for regex. And we need the index of ".5". | ||
const capturingGroupIndex = match[0].length - match[1].length | ||
return | ||
} | ||
const index = valueNode.sourceIndex + match.index + capturingGroupIndex | ||
const baseIndex = isAtRule(node) ? atRuleParamIndex(node) : declarationValueIndex(node) | ||
if (context.fix) { | ||
alwaysFixPositions.unshift({ | ||
index, | ||
}) | ||
complain(messages.expected, node, baseIndex + index) | ||
} | ||
return | ||
} | ||
if (primary === `never`) { | ||
const match = /(?:\D|^)(0+)(\.\d+)/.exec(valueNode.value) | ||
const baseIndex = isAtRule(node) ? atRuleParamIndex(node) : declarationValueIndex(node) | ||
if (match === null || match[0] === null || match[1] === null || match[2] === null) { | ||
return | ||
complain(messages.expected, node, baseIndex + index) | ||
} | ||
// The regexp above consists of 3 capturing groups (or capturing parentheses). | ||
// We need the index of the second group. This makes sanse when we have "-00.5" | ||
// as an input for regex. And we need the index of "00". | ||
const capturingGroupIndex = match[0].length - (match[1].length + match[2].length) | ||
if (primary === `never`) { | ||
const match = (/(?:\D|^)(0+)(\.\d+)/).exec(valueNode.value) | ||
const index = valueNode.sourceIndex + match.index + capturingGroupIndex | ||
if (match === null || match[0] === null || match[1] === null || match[2] === null) { | ||
return | ||
} | ||
if (context.fix) { | ||
neverFixPositions.unshift({ | ||
startIndex: index, | ||
// match[1].length is the length of our matched zero(s) | ||
endIndex: index + match[1].length, | ||
}) | ||
// The regexp above consists of 3 capturing groups (or capturing parentheses). | ||
// We need the index of the second group. This makes sanse when we have "-00.5" | ||
// as an input for regex. And we need the index of "00". | ||
const capturingGroupIndex = match[0].length - (match[1].length + match[2].length) | ||
return | ||
} | ||
const index = valueNode.sourceIndex + match.index + capturingGroupIndex | ||
const baseIndex = isAtRule(node) ? atRuleParamIndex(node) : declarationValueIndex(node) | ||
if (context.fix) { | ||
neverFixPositions.unshift({ | ||
startIndex: index, | ||
// match[1].length is the length of our matched zero(s) | ||
endIndex: index + match[1].length, | ||
}) | ||
complain(messages.rejected, node, baseIndex + index) | ||
} | ||
}) | ||
return | ||
} | ||
if (alwaysFixPositions.length) { | ||
for (const fixPosition of alwaysFixPositions) { | ||
const index = fixPosition.index | ||
const baseIndex = isAtRule(node) ? atRuleParamIndex(node) : declarationValueIndex(node) | ||
if (isAtRule(node)) { | ||
node.params = addLeadingZero(node.params, index) | ||
} else { | ||
node.value = addLeadingZero(node.value, index) | ||
complain(messages.rejected, node, baseIndex + index) | ||
} | ||
}) | ||
if (alwaysFixPositions.length) { | ||
for (const fixPosition of alwaysFixPositions) { | ||
const index = fixPosition.index | ||
if (isAtRule(node)) { | ||
node.params = addLeadingZero(node.params, index) | ||
} else { | ||
node.value = addLeadingZero(node.value, index) | ||
} | ||
} | ||
} | ||
} | ||
if (neverFixPositions.length) { | ||
for (const fixPosition of neverFixPositions) { | ||
const startIndex = fixPosition.startIndex | ||
const endIndex = fixPosition.endIndex | ||
if (neverFixPositions.length) { | ||
for (const fixPosition of neverFixPositions) { | ||
const startIndex = fixPosition.startIndex | ||
const endIndex = fixPosition.endIndex | ||
if (isAtRule(node)) { | ||
node.params = removeLeadingZeros(node.params, startIndex, endIndex) | ||
} else { | ||
node.value = removeLeadingZeros(node.value, startIndex, endIndex) | ||
if (isAtRule(node)) { | ||
node.params = removeLeadingZeros(node.params, startIndex, endIndex) | ||
} else { | ||
node.value = removeLeadingZeros(node.value, startIndex, endIndex) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* @param {string} message | ||
* @param {import('postcss').Node} node | ||
* @param {number} index | ||
*/ | ||
function complain (message, node, index) { | ||
report({ | ||
result, | ||
ruleName, | ||
message, | ||
node, | ||
index, | ||
}) | ||
/** | ||
* @param {string} message | ||
* @param {import('postcss').Node} node | ||
* @param {number} index | ||
*/ | ||
function complain (message, node, index) { | ||
report({ | ||
result, | ||
ruleName, | ||
message, | ||
node, | ||
index, | ||
}) | ||
} | ||
} | ||
@@ -196,2 +199,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import valueParser from "postcss-value-parser" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import { isAtRule } from "../../utils/typeGuards.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -26,96 +26,98 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { actual: primary }) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { actual: primary }) | ||
if (!validOptions) { | ||
return | ||
} | ||
root.walkAtRules((atRule) => { | ||
if (atRule.name.toLowerCase() === `import`) { | ||
if (!validOptions) { | ||
return | ||
} | ||
check(atRule, atRule.params) | ||
}) | ||
root.walkAtRules((atRule) => { | ||
if (atRule.name.toLowerCase() === `import`) { | ||
return | ||
} | ||
root.walkDecls((decl) => check(decl, decl.value)) | ||
check(atRule, atRule.params) | ||
}) | ||
/** | ||
* @param {import('postcss').AtRule | import('postcss').Declaration} node | ||
* @param {string} value | ||
*/ | ||
function check (node, value) { | ||
/** @type {Array<{ startIndex: number, endIndex: number }>} */ | ||
const fixPositions = [] | ||
root.walkDecls((decl) => check(decl, decl.value)) | ||
// Get out quickly if there are no periods | ||
if (!value.includes(`.`)) { | ||
return | ||
} | ||
/** | ||
* @param {import('postcss').AtRule | import('postcss').Declaration} node | ||
* @param {string} value | ||
*/ | ||
function check (node, value) { | ||
/** @type {Array<{ startIndex: number, endIndex: number }>} */ | ||
const fixPositions = [] | ||
valueParser(value).walk((valueNode) => { | ||
// Ignore `url` function | ||
if (valueNode.type === `function` && valueNode.value.toLowerCase() === `url`) { | ||
return false | ||
} | ||
// Ignore strings, comments, etc | ||
if (valueNode.type !== `word`) { | ||
// Get out quickly if there are no periods | ||
if (!value.includes(`.`)) { | ||
return | ||
} | ||
const match = /\.(\d{0,100}?)(0+)(?:\D|$)/.exec(valueNode.value) | ||
valueParser(value).walk((valueNode) => { | ||
// Ignore `url` function | ||
if (valueNode.type === `function` && valueNode.value.toLowerCase() === `url`) { | ||
return false | ||
} | ||
// match[1] is any numbers between the decimal and our trailing zero, could be empty | ||
// match[2] is our trailing zero(s) | ||
if (match === null || match[1] === null || match[2] === null) { | ||
return | ||
} | ||
// Ignore strings, comments, etc | ||
if (valueNode.type !== `word`) { | ||
return | ||
} | ||
// our index is: | ||
// the index of our valueNode + | ||
// the index of our match + | ||
// 1 for our decimal + | ||
// the length of our potential non-zero number match (match[1]) | ||
const index = valueNode.sourceIndex + match.index + 1 + match[1].length | ||
const match = (/\.(\d{0,100}?)(0+)(?:\D|$)/).exec(valueNode.value) | ||
// our startIndex is identical to our index except when we have only | ||
// trailing zeros after our decimal. in that case we don't need the decimal | ||
// either so we move our index back by 1. | ||
const startIndex = match[1].length > 0 ? index : index - 1 | ||
// match[1] is any numbers between the decimal and our trailing zero, could be empty | ||
// match[2] is our trailing zero(s) | ||
if (match === null || match[1] === null || match[2] === null) { | ||
return | ||
} | ||
// our end index is our original index + the length of our trailing zeros | ||
const endIndex = index + match[2].length | ||
// our index is: | ||
// the index of our valueNode + | ||
// the index of our match + | ||
// 1 for our decimal + | ||
// the length of our potential non-zero number match (match[1]) | ||
const index = valueNode.sourceIndex + match.index + 1 + match[1].length | ||
if (context.fix) { | ||
fixPositions.unshift({ | ||
startIndex, | ||
endIndex, | ||
}) | ||
// our startIndex is identical to our index except when we have only | ||
// trailing zeros after our decimal. in that case we don't need the decimal | ||
// either so we move our index back by 1. | ||
const startIndex = match[1].length > 0 ? index : index - 1 | ||
return | ||
} | ||
// our end index is our original index + the length of our trailing zeros | ||
const endIndex = index + match[2].length | ||
const baseIndex = isAtRule(node) ? atRuleParamIndex(node) : declarationValueIndex(node) | ||
if (context.fix) { | ||
fixPositions.unshift({ | ||
startIndex, | ||
endIndex, | ||
}) | ||
report({ | ||
message: messages.rejected, | ||
node, | ||
// this is the index of the _first_ trailing zero | ||
index: baseIndex + index, | ||
result, | ||
ruleName, | ||
return | ||
} | ||
const baseIndex = isAtRule(node) ? atRuleParamIndex(node) : declarationValueIndex(node) | ||
report({ | ||
message: messages.rejected, | ||
node, | ||
// this is the index of the _first_ trailing zero | ||
index: baseIndex + index, | ||
result, | ||
ruleName, | ||
}) | ||
}) | ||
}) | ||
if (fixPositions.length) { | ||
for (const fixPosition of fixPositions) { | ||
const startIndex = fixPosition.startIndex | ||
const endIndex = fixPosition.endIndex | ||
if (fixPositions.length) { | ||
for (const fixPosition of fixPositions) { | ||
const startIndex = fixPosition.startIndex | ||
const endIndex = fixPosition.endIndex | ||
if (isAtRule(node)) { | ||
node.params = removeTrailingZeros(node.params, startIndex, endIndex) | ||
} else { | ||
node.value = removeTrailingZeros(node.value, startIndex, endIndex) | ||
if (isAtRule(node)) { | ||
node.params = removeTrailingZeros(node.params, startIndex, endIndex) | ||
} else { | ||
node.value = removeTrailingZeros(node.value, startIndex, endIndex) | ||
} | ||
} | ||
@@ -140,2 +142,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isCustomProperty from "../../utils/isCustomProperty.js" | ||
import { isRule } from "../../utils/typeGuards.js" | ||
import isStandardSyntaxProperty from "../../utils/isStandardSyntaxProperty.js" | ||
import optionsMatches from "../../utils/optionsMatches.js" | ||
import { isRegExp, isString } from "../../utils/validateTypes.js" | ||
import { isRule } from "../../utils/typeGuards.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,68 +27,70 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}, | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
ignoreSelectors: [isString, isRegExp], | ||
function rule (primary, secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions( | ||
result, | ||
ruleName, | ||
{ | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}, | ||
optional: true, | ||
}, | ||
) | ||
{ | ||
actual: secondaryOptions, | ||
possible: { | ||
ignoreSelectors: [isString, isRegExp], | ||
}, | ||
optional: true, | ||
}, | ||
) | ||
if (!validOptions) { | ||
return | ||
} | ||
root.walkDecls((decl) => { | ||
const prop = decl.prop | ||
if (!isStandardSyntaxProperty(prop)) { | ||
if (!validOptions) { | ||
return | ||
} | ||
if (isCustomProperty(prop)) { | ||
return | ||
} | ||
root.walkDecls((decl) => { | ||
const prop = decl.prop | ||
const { parent } = decl | ||
if (!isStandardSyntaxProperty(prop)) { | ||
return | ||
} | ||
if (!parent) { | ||
throw new Error(`A parent node must be present`) | ||
} | ||
if (isCustomProperty(prop)) { | ||
return | ||
} | ||
if (isRule(parent)) { | ||
const { selector } = parent | ||
const { parent } = decl | ||
if (selector && optionsMatches(secondaryOptions, `ignoreSelectors`, selector)) { | ||
return | ||
if (!parent) { | ||
throw new Error(`A parent node must be present`) | ||
} | ||
} | ||
const expectedProp = primary === `lower` ? prop.toLowerCase() : prop.toUpperCase() | ||
if (isRule(parent)) { | ||
const { selector } = parent | ||
if (prop === expectedProp) { | ||
return | ||
} | ||
if (selector && optionsMatches(secondaryOptions, `ignoreSelectors`, selector)) { | ||
return | ||
} | ||
} | ||
if (context.fix) { | ||
decl.prop = expectedProp | ||
const expectedProp = primary === `lower` ? prop.toLowerCase() : prop.toUpperCase() | ||
return | ||
} | ||
if (prop === expectedProp) { | ||
return | ||
} | ||
report({ | ||
message: messages.expected(prop, expectedProp), | ||
word: prop, | ||
node: decl, | ||
ruleName, | ||
result, | ||
if (context.fix) { | ||
decl.prop = expectedProp | ||
return | ||
} | ||
report({ | ||
message: messages.expected(prop, expectedProp), | ||
word: prop, | ||
node: decl, | ||
ruleName, | ||
result, | ||
}) | ||
}) | ||
}) | ||
} | ||
} | ||
@@ -99,2 +101,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import styleSearch from "style-search" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isStandardSyntaxRule from "../../utils/isStandardSyntaxRule.js" | ||
import parseSelector from "../../utils/parseSelector.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -28,3 +28,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
@@ -144,16 +144,17 @@ const validOptions = validateOptions(result, ruleName, { | ||
/** @type {{ attrBefore: string, setAttrBefore: (fixed: string) => void }} */ | ||
const { attrBefore, setAttrBefore } = rawAttrBefore ? { | ||
attrBefore: rawAttrBefore, | ||
setAttrBefore (fixed) { | ||
spacesAttribute.before = fixed | ||
}, | ||
} : { | ||
attrBefore: | ||
(attributeNode.spaces.attribute && attributeNode.spaces.attribute.before) || ``, | ||
setAttrBefore (fixed) { | ||
if (!attributeNode.spaces.attribute) {attributeNode.spaces.attribute = {}} | ||
const { attrBefore, setAttrBefore } = rawAttrBefore | ||
? { | ||
attrBefore: rawAttrBefore, | ||
setAttrBefore (fixed) { | ||
spacesAttribute.before = fixed | ||
}, | ||
} | ||
: { | ||
attrBefore: (attributeNode.spaces.attribute && attributeNode.spaces.attribute.before) || ``, | ||
setAttrBefore (fixed) { | ||
if (!attributeNode.spaces.attribute) { attributeNode.spaces.attribute = {} } | ||
attributeNode.spaces.attribute.before = fixed | ||
}, | ||
} | ||
attributeNode.spaces.attribute.before = fixed | ||
}, | ||
} | ||
@@ -180,16 +181,18 @@ if (primary === `always`) { | ||
/** @type {{ after: string, setAfter: (fixed: string) => void }} */ | ||
const { after, setAfter } = rawAfter ? { | ||
after: rawAfter, | ||
setAfter (fixed) { | ||
rawSpaces.after = fixed | ||
}, | ||
} : { | ||
after: (spaces && spaces.after) || ``, | ||
setAfter (fixed) { | ||
if (!attributeNode.spaces[key]) {attributeNode.spaces[key] = {}} | ||
const { after, setAfter } = rawAfter | ||
? { | ||
after: rawAfter, | ||
setAfter (fixed) { | ||
rawSpaces.after = fixed | ||
}, | ||
} | ||
: { | ||
after: (spaces && spaces.after) || ``, | ||
setAfter (fixed) { | ||
if (!attributeNode.spaces[key]) { attributeNode.spaces[key] = {} } | ||
// @ts-expect-error -- TS2532: Object is possibly 'undefined'. | ||
attributeNode.spaces[key].after = fixed | ||
}, | ||
} | ||
// @ts-expect-error -- TS2532: Object is possibly 'undefined'. | ||
attributeNode.spaces[key].after = fixed | ||
}, | ||
} | ||
@@ -207,2 +210,3 @@ if (primary === `always`) { | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import selectorAttributeOperatorSpaceChecker from "../../utils/selectorAttributeOperatorSpaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -25,80 +25,82 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
if (!validOptions) { | ||
return | ||
} | ||
selectorAttributeOperatorSpaceChecker({ | ||
root, | ||
result, | ||
locationChecker: checker.after, | ||
checkedRuleName: ruleName, | ||
checkBeforeOperator: false, | ||
fix: context.fix ? (attributeNode) => { | ||
/** @type {{ operatorAfter: string, setOperatorAfter: (fixed: string) => void }} */ | ||
const { operatorAfter, setOperatorAfter } = (() => { | ||
const rawOperator = attributeNode.raws.operator | ||
selectorAttributeOperatorSpaceChecker({ | ||
root, | ||
result, | ||
locationChecker: checker.after, | ||
checkedRuleName: ruleName, | ||
checkBeforeOperator: false, | ||
fix: context.fix | ||
? (attributeNode) => { | ||
/** @type {{ operatorAfter: string, setOperatorAfter: (fixed: string) => void }} */ | ||
const { operatorAfter, setOperatorAfter } = (() => { | ||
const rawOperator = attributeNode.raws.operator | ||
if (rawOperator) { | ||
return { | ||
operatorAfter: rawOperator.slice( | ||
attributeNode.operator ? attributeNode.operator.length : 0, | ||
), | ||
setOperatorAfter (fixed) { | ||
delete attributeNode.raws.operator | ||
if (rawOperator) { | ||
return { | ||
operatorAfter: rawOperator.slice( | ||
attributeNode.operator ? attributeNode.operator.length : 0, | ||
), | ||
setOperatorAfter (fixed) { | ||
delete attributeNode.raws.operator | ||
if (!attributeNode.raws.spaces) {attributeNode.raws.spaces = {}} | ||
if (!attributeNode.raws.spaces) { attributeNode.raws.spaces = {} } | ||
if (!attributeNode.raws.spaces.operator) | ||
{attributeNode.raws.spaces.operator = {}} | ||
if (!attributeNode.raws.spaces.operator) { attributeNode.raws.spaces.operator = {} } | ||
attributeNode.raws.spaces.operator.after = fixed | ||
}, | ||
} | ||
} | ||
attributeNode.raws.spaces.operator.after = fixed | ||
}, | ||
} | ||
} | ||
const rawSpacesOperator = attributeNode.raws.spaces && attributeNode.raws.spaces.operator | ||
const rawOperatorAfter = rawSpacesOperator && rawSpacesOperator.after | ||
const rawSpacesOperator = attributeNode.raws.spaces && attributeNode.raws.spaces.operator | ||
const rawOperatorAfter = rawSpacesOperator && rawSpacesOperator.after | ||
if (rawOperatorAfter) { | ||
return { | ||
operatorAfter: rawOperatorAfter, | ||
setOperatorAfter (fixed) { | ||
rawSpacesOperator.after = fixed | ||
}, | ||
} | ||
} | ||
if (rawOperatorAfter) { | ||
return { | ||
operatorAfter: rawOperatorAfter, | ||
setOperatorAfter (fixed) { | ||
rawSpacesOperator.after = fixed | ||
}, | ||
} | ||
} | ||
return { | ||
operatorAfter: | ||
(attributeNode.spaces.operator && attributeNode.spaces.operator.after) || ``, | ||
setOperatorAfter (fixed) { | ||
if (!attributeNode.spaces.operator) {attributeNode.spaces.operator = {}} | ||
return { | ||
operatorAfter: (attributeNode.spaces.operator && attributeNode.spaces.operator.after) || ``, | ||
setOperatorAfter (fixed) { | ||
if (!attributeNode.spaces.operator) { attributeNode.spaces.operator = {} } | ||
attributeNode.spaces.operator.after = fixed | ||
}, | ||
} | ||
})() | ||
attributeNode.spaces.operator.after = fixed | ||
}, | ||
} | ||
})() | ||
if (primary === `always`) { | ||
setOperatorAfter(operatorAfter.replace(/^\s*/, ` `)) | ||
if (primary === `always`) { | ||
setOperatorAfter(operatorAfter.replace(/^\s*/, ` `)) | ||
return true | ||
} | ||
return true | ||
} | ||
if (primary === `never`) { | ||
setOperatorAfter(operatorAfter.replace(/^\s*/, ``)) | ||
if (primary === `never`) { | ||
setOperatorAfter(operatorAfter.replace(/^\s*/, ``)) | ||
return true | ||
} | ||
return true | ||
} | ||
return false | ||
} : null, | ||
}) | ||
return false | ||
} | ||
: null, | ||
}) | ||
} | ||
} | ||
@@ -109,2 +111,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import selectorAttributeOperatorSpaceChecker from "../../utils/selectorAttributeOperatorSpaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -25,3 +25,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -45,36 +45,39 @@ | ||
checkBeforeOperator: true, | ||
fix: context.fix ? (attributeNode) => { | ||
const rawAttr = attributeNode.raws.spaces && attributeNode.raws.spaces.attribute | ||
const rawAttrAfter = rawAttr && rawAttr.after | ||
fix: context.fix | ||
? (attributeNode) => { | ||
const rawAttr = attributeNode.raws.spaces && attributeNode.raws.spaces.attribute | ||
const rawAttrAfter = rawAttr && rawAttr.after | ||
/** @type {{ attrAfter: string, setAttrAfter: (fixed: string) => void }} */ | ||
const { attrAfter, setAttrAfter } = rawAttrAfter ? { | ||
attrAfter: rawAttrAfter, | ||
setAttrAfter (fixed) { | ||
rawAttr.after = fixed | ||
}, | ||
} : { | ||
attrAfter: | ||
(attributeNode.spaces.attribute && attributeNode.spaces.attribute.after) || ``, | ||
setAttrAfter (fixed) { | ||
if (!attributeNode.spaces.attribute) {attributeNode.spaces.attribute = {}} | ||
/** @type {{ attrAfter: string, setAttrAfter: (fixed: string) => void }} */ | ||
const { attrAfter, setAttrAfter } = rawAttrAfter | ||
? { | ||
attrAfter: rawAttrAfter, | ||
setAttrAfter (fixed) { | ||
rawAttr.after = fixed | ||
}, | ||
} | ||
: { | ||
attrAfter: (attributeNode.spaces.attribute && attributeNode.spaces.attribute.after) || ``, | ||
setAttrAfter (fixed) { | ||
if (!attributeNode.spaces.attribute) { attributeNode.spaces.attribute = {} } | ||
attributeNode.spaces.attribute.after = fixed | ||
}, | ||
} | ||
attributeNode.spaces.attribute.after = fixed | ||
}, | ||
} | ||
if (primary === `always`) { | ||
setAttrAfter(attrAfter.replace(/\s*$/, ` `)) | ||
if (primary === `always`) { | ||
setAttrAfter(attrAfter.replace(/\s*$/, ` `)) | ||
return true | ||
} | ||
return true | ||
} | ||
if (primary === `never`) { | ||
setAttrAfter(attrAfter.replace(/\s*$/, ``)) | ||
if (primary === `never`) { | ||
setAttrAfter(attrAfter.replace(/\s*$/, ``)) | ||
return true | ||
return true | ||
} | ||
return false | ||
} | ||
return false | ||
} : null, | ||
: null, | ||
}) | ||
@@ -87,2 +90,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import selectorCombinatorSpaceChecker from "../../utils/selectorCombinatorSpaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -25,3 +25,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -45,17 +45,19 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (combinator) => { | ||
if (primary === `always`) { | ||
combinator.spaces.after = ` ` | ||
fix: context.fix | ||
? (combinator) => { | ||
if (primary === `always`) { | ||
combinator.spaces.after = ` ` | ||
return true | ||
} | ||
return true | ||
} | ||
if (primary === `never`) { | ||
combinator.spaces.after = `` | ||
if (primary === `never`) { | ||
combinator.spaces.after = `` | ||
return true | ||
return true | ||
} | ||
return false | ||
} | ||
return false | ||
} : null, | ||
: null, | ||
}) | ||
@@ -68,2 +70,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import selectorCombinatorSpaceChecker from "../../utils/selectorCombinatorSpaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -25,3 +25,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -45,17 +45,19 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (combinator) => { | ||
if (primary === `always`) { | ||
combinator.spaces.before = ` ` | ||
fix: context.fix | ||
? (combinator) => { | ||
if (primary === `always`) { | ||
combinator.spaces.before = ` ` | ||
return true | ||
} | ||
return true | ||
} | ||
if (primary === `never`) { | ||
combinator.spaces.before = `` | ||
if (primary === `never`) { | ||
combinator.spaces.before = `` | ||
return true | ||
return true | ||
} | ||
return false | ||
} | ||
return false | ||
} : null, | ||
: null, | ||
}) | ||
@@ -68,2 +70,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isStandardSyntaxRule from "../../utils/isStandardSyntaxRule.js" | ||
import parseSelector from "../../utils/parseSelector.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -24,63 +24,65 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
root.walkRules((ruleNode) => { | ||
if (!isStandardSyntaxRule(ruleNode)) { | ||
if (!validOptions) { | ||
return | ||
} | ||
let hasFixed = false | ||
const selector = ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector | ||
root.walkRules((ruleNode) => { | ||
if (!isStandardSyntaxRule(ruleNode)) { | ||
return | ||
} | ||
// Return early for selectors containing comments | ||
// TODO: re-enable when parser and stylelint are compatible | ||
if (selector.includes(`/*`)) {return} | ||
let hasFixed = false | ||
const selector = ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector | ||
const fixedSelector = parseSelector(selector, result, ruleNode, (fullSelector) => { | ||
fullSelector.walkCombinators((combinatorNode) => { | ||
if (combinatorNode.value !== ` `) { | ||
return | ||
} | ||
// Return early for selectors containing comments | ||
// TODO: re-enable when parser and stylelint are compatible | ||
if (selector.includes(`/*`)) { return } | ||
const value = combinatorNode.toString() | ||
const fixedSelector = parseSelector(selector, result, ruleNode, (fullSelector) => { | ||
fullSelector.walkCombinators((combinatorNode) => { | ||
if (combinatorNode.value !== ` `) { | ||
return | ||
} | ||
if (value.includes(` `) || value.includes(`\t`) || value.includes(`\n`) || value.includes(`\r`)) { | ||
if (context.fix && /^\s+$/.test(value)) { | ||
hasFixed = true | ||
const value = combinatorNode.toString() | ||
if (!combinatorNode.raws) {combinatorNode.raws = {}} | ||
if (value.includes(` `) || value.includes(`\t`) || value.includes(`\n`) || value.includes(`\r`)) { | ||
if (context.fix && (/^\s+$/).test(value)) { | ||
hasFixed = true | ||
combinatorNode.raws.value = ` ` | ||
combinatorNode.rawSpaceBefore = combinatorNode.rawSpaceBefore.replace(/^\s+/, ``) | ||
combinatorNode.rawSpaceAfter = combinatorNode.rawSpaceAfter.replace(/\s+$/, ``) | ||
if (!combinatorNode.raws) { combinatorNode.raws = {} } | ||
return | ||
combinatorNode.raws.value = ` ` | ||
combinatorNode.rawSpaceBefore = combinatorNode.rawSpaceBefore.replace(/^\s+/, ``) | ||
combinatorNode.rawSpaceAfter = combinatorNode.rawSpaceAfter.replace(/\s+$/, ``) | ||
return | ||
} | ||
report({ | ||
result, | ||
ruleName, | ||
message: messages.rejected(value), | ||
node: ruleNode, | ||
index: combinatorNode.sourceIndex, | ||
}) | ||
} | ||
}) | ||
}) | ||
report({ | ||
result, | ||
ruleName, | ||
message: messages.rejected(value), | ||
node: ruleNode, | ||
index: combinatorNode.sourceIndex, | ||
}) | ||
if (hasFixed && fixedSelector) { | ||
if (!ruleNode.raws.selector) { | ||
ruleNode.selector = fixedSelector | ||
} else { | ||
ruleNode.raws.selector.raw = fixedSelector | ||
} | ||
}) | ||
} | ||
}) | ||
if (hasFixed && fixedSelector) { | ||
if (!ruleNode.raws.selector) { | ||
ruleNode.selector = fixedSelector | ||
} else { | ||
ruleNode.raws.selector.raw = fixedSelector | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
@@ -91,2 +93,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import styleSearch from "style-search" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isStandardSyntaxRule from "../../utils/isStandardSyntaxRule.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,3 +27,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -65,3 +65,3 @@ | ||
// ending the comment so we're fine | ||
if (/^\s+\/\//.test(nextChars)) { | ||
if ((/^\s+\/\//).test(nextChars)) { | ||
return | ||
@@ -71,3 +71,3 @@ } | ||
// If there are spaces and then a comment begins, look for the newline | ||
const indextoCheckAfter = /^\s+\/\*/.test(nextChars) ? selector.indexOf(`*/`, match.endIndex) + 1 : match.startIndex | ||
const indextoCheckAfter = (/^\s+\/\*/).test(nextChars) ? selector.indexOf(`*/`, match.endIndex) + 1 : match.startIndex | ||
@@ -125,2 +125,3 @@ checker.afterOneOnly({ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import selectorListCommaWhitespaceChecker from "../../utils/selectorListCommaWhitespaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -26,3 +26,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -48,11 +48,14 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (ruleNode, index) => { | ||
fixData = fixData || new Map() | ||
const commaIndices = fixData.get(ruleNode) || [] | ||
fix: context.fix | ||
? (ruleNode, index) => { | ||
fixData = fixData || (new Map) | ||
commaIndices.push(index) | ||
fixData.set(ruleNode, commaIndices) | ||
const commaIndices = fixData.get(ruleNode) || [] | ||
return true | ||
} : null, | ||
commaIndices.push(index) | ||
fixData.set(ruleNode, commaIndices) | ||
return true | ||
} | ||
: null, | ||
}) | ||
@@ -92,2 +95,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import selectorListCommaWhitespaceChecker from "../../utils/selectorListCommaWhitespaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,3 +27,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -49,11 +49,14 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (ruleNode, index) => { | ||
fixData = fixData || new Map() | ||
const commaIndices = fixData.get(ruleNode) || [] | ||
fix: context.fix | ||
? (ruleNode, index) => { | ||
fixData = fixData || (new Map) | ||
commaIndices.push(index) | ||
fixData.set(ruleNode, commaIndices) | ||
const commaIndices = fixData.get(ruleNode) || [] | ||
return true | ||
} : null, | ||
commaIndices.push(index) | ||
fixData.set(ruleNode, commaIndices) | ||
return true | ||
} | ||
: null, | ||
}) | ||
@@ -91,2 +94,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import selectorListCommaWhitespaceChecker from "../../utils/selectorListCommaWhitespaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,3 +27,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -49,11 +49,14 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (ruleNode, index) => { | ||
fixData = fixData || new Map() | ||
const commaIndices = fixData.get(ruleNode) || [] | ||
fix: context.fix | ||
? (ruleNode, index) => { | ||
fixData = fixData || (new Map) | ||
commaIndices.push(index) | ||
fixData.set(ruleNode, commaIndices) | ||
const commaIndices = fixData.get(ruleNode) || [] | ||
return true | ||
} : null, | ||
commaIndices.push(index) | ||
fixData.set(ruleNode, commaIndices) | ||
return true | ||
} | ||
: null, | ||
}) | ||
@@ -91,2 +94,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { isNumber } from "../../utils/validateTypes.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import { isNumber } from "../../utils/validateTypes.js" | ||
@@ -23,3 +23,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const maxAdjacentNewlines = primary + 1 | ||
@@ -55,5 +55,3 @@ | ||
} | ||
} else if ( | ||
violatedLFNewLinesRegex.test(selector) || violatedCRLFNewLinesRegex.test(selector) | ||
) { | ||
} else if (violatedLFNewLinesRegex.test(selector) || violatedCRLFNewLinesRegex.test(selector)) { | ||
report({ | ||
@@ -74,2 +72,3 @@ message: messages.expected(primary), | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isStandardSyntaxRule from "../../utils/isStandardSyntaxRule.js" | ||
@@ -7,4 +9,2 @@ import isStandardSyntaxSelector from "../../utils/isStandardSyntaxSelector.js" | ||
import parseSelector from "../../utils/parseSelector.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,70 +27,72 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
root.walkRules((ruleNode) => { | ||
if (!isStandardSyntaxRule(ruleNode)) { | ||
if (!validOptions) { | ||
return | ||
} | ||
const selector = ruleNode.selector | ||
root.walkRules((ruleNode) => { | ||
if (!isStandardSyntaxRule(ruleNode)) { | ||
return | ||
} | ||
if (!selector.includes(`:`)) { | ||
return | ||
} | ||
const selector = ruleNode.selector | ||
const fixedSelector = parseSelector( | ||
ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector, | ||
result, | ||
ruleNode, | ||
(selectorTree) => { | ||
selectorTree.walkPseudos((pseudoNode) => { | ||
const pseudo = pseudoNode.value | ||
if (!selector.includes(`:`)) { | ||
return | ||
} | ||
if (!isStandardSyntaxSelector(pseudo)) { | ||
return | ||
} | ||
const fixedSelector = parseSelector( | ||
ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector, | ||
result, | ||
ruleNode, | ||
(selectorTree) => { | ||
selectorTree.walkPseudos((pseudoNode) => { | ||
const pseudo = pseudoNode.value | ||
if (pseudo.includes(`::`) || levelOneAndTwoPseudoElements.has(pseudo.toLowerCase().slice(1))) { | ||
return | ||
} | ||
if (!isStandardSyntaxSelector(pseudo)) { | ||
return | ||
} | ||
const expectedPseudo = primary === `lower` ? pseudo.toLowerCase() : pseudo.toUpperCase() | ||
if (pseudo.includes(`::`) || levelOneAndTwoPseudoElements.has(pseudo.toLowerCase().slice(1))) { | ||
return | ||
} | ||
if (pseudo === expectedPseudo) { | ||
return | ||
} | ||
const expectedPseudo = primary === `lower` ? pseudo.toLowerCase() : pseudo.toUpperCase() | ||
if (context.fix) { | ||
pseudoNode.value = expectedPseudo | ||
if (pseudo === expectedPseudo) { | ||
return | ||
} | ||
return | ||
} | ||
if (context.fix) { | ||
pseudoNode.value = expectedPseudo | ||
report({ | ||
message: messages.expected(pseudo, expectedPseudo), | ||
node: ruleNode, | ||
index: pseudoNode.sourceIndex, | ||
ruleName, | ||
result, | ||
return | ||
} | ||
report({ | ||
message: messages.expected(pseudo, expectedPseudo), | ||
node: ruleNode, | ||
index: pseudoNode.sourceIndex, | ||
ruleName, | ||
result, | ||
}) | ||
}) | ||
}) | ||
}, | ||
) | ||
}, | ||
) | ||
if (context.fix && fixedSelector) { | ||
if (ruleNode.raws.selector) { | ||
ruleNode.raws.selector.raw = fixedSelector | ||
} else { | ||
ruleNode.selector = fixedSelector | ||
if (context.fix && fixedSelector) { | ||
if (ruleNode.raws.selector) { | ||
ruleNode.raws.selector.raw = fixedSelector | ||
} else { | ||
ruleNode.selector = fixedSelector | ||
} | ||
} | ||
} | ||
}) | ||
}) | ||
} | ||
} | ||
@@ -101,2 +103,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isStandardSyntaxRule from "../../utils/isStandardSyntaxRule.js" | ||
import parseSelector from "../../utils/parseSelector.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,96 +27,98 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
root.walkRules((ruleNode) => { | ||
if (!isStandardSyntaxRule(ruleNode)) { | ||
if (!validOptions) { | ||
return | ||
} | ||
if (!ruleNode.selector.includes(`(`)) { | ||
return | ||
} | ||
root.walkRules((ruleNode) => { | ||
if (!isStandardSyntaxRule(ruleNode)) { | ||
return | ||
} | ||
let hasFixed = false | ||
const selector = ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector | ||
const fixedSelector = parseSelector(selector, result, ruleNode, (selectorTree) => { | ||
selectorTree.walkPseudos((pseudoNode) => { | ||
if (!pseudoNode.length) { | ||
return | ||
} | ||
if (!ruleNode.selector.includes(`(`)) { | ||
return | ||
} | ||
const paramString = pseudoNode.map((node) => String(node)).join(`,`) | ||
const nextCharIsSpace = paramString.startsWith(` `) | ||
const openIndex = pseudoNode.sourceIndex + pseudoNode.value.length + 1 | ||
let hasFixed = false | ||
const selector = ruleNode.raws.selector ? ruleNode.raws.selector.raw : ruleNode.selector | ||
const fixedSelector = parseSelector(selector, result, ruleNode, (selectorTree) => { | ||
selectorTree.walkPseudos((pseudoNode) => { | ||
if (!pseudoNode.length) { | ||
return | ||
} | ||
if (nextCharIsSpace && primary === `never`) { | ||
if (context.fix) { | ||
hasFixed = true | ||
setFirstNodeSpaceBefore(pseudoNode, ``) | ||
} else { | ||
complain(messages.rejectedOpening, openIndex) | ||
const paramString = pseudoNode.map((node) => String(node)).join(`,`) | ||
const nextCharIsSpace = paramString.startsWith(` `) | ||
const openIndex = pseudoNode.sourceIndex + pseudoNode.value.length + 1 | ||
if (nextCharIsSpace && primary === `never`) { | ||
if (context.fix) { | ||
hasFixed = true | ||
setFirstNodeSpaceBefore(pseudoNode, ``) | ||
} else { | ||
complain(messages.rejectedOpening, openIndex) | ||
} | ||
} | ||
} | ||
if (!nextCharIsSpace && primary === `always`) { | ||
if (context.fix) { | ||
hasFixed = true | ||
setFirstNodeSpaceBefore(pseudoNode, ` `) | ||
} else { | ||
complain(messages.expectedOpening, openIndex) | ||
if (!nextCharIsSpace && primary === `always`) { | ||
if (context.fix) { | ||
hasFixed = true | ||
setFirstNodeSpaceBefore(pseudoNode, ` `) | ||
} else { | ||
complain(messages.expectedOpening, openIndex) | ||
} | ||
} | ||
} | ||
const prevCharIsSpace = paramString.endsWith(` `) | ||
const closeIndex = openIndex + paramString.length - 1 | ||
const prevCharIsSpace = paramString.endsWith(` `) | ||
const closeIndex = openIndex + paramString.length - 1 | ||
if (prevCharIsSpace && primary === `never`) { | ||
if (context.fix) { | ||
hasFixed = true | ||
setLastNodeSpaceAfter(pseudoNode, ``) | ||
} else { | ||
complain(messages.rejectedClosing, closeIndex) | ||
if (prevCharIsSpace && primary === `never`) { | ||
if (context.fix) { | ||
hasFixed = true | ||
setLastNodeSpaceAfter(pseudoNode, ``) | ||
} else { | ||
complain(messages.rejectedClosing, closeIndex) | ||
} | ||
} | ||
} | ||
if (!prevCharIsSpace && primary === `always`) { | ||
if (context.fix) { | ||
hasFixed = true | ||
setLastNodeSpaceAfter(pseudoNode, ` `) | ||
} else { | ||
complain(messages.expectedClosing, closeIndex) | ||
if (!prevCharIsSpace && primary === `always`) { | ||
if (context.fix) { | ||
hasFixed = true | ||
setLastNodeSpaceAfter(pseudoNode, ` `) | ||
} else { | ||
complain(messages.expectedClosing, closeIndex) | ||
} | ||
} | ||
} | ||
}) | ||
}) | ||
}) | ||
if (hasFixed && fixedSelector) { | ||
if (!ruleNode.raws.selector) { | ||
ruleNode.selector = fixedSelector | ||
} else { | ||
ruleNode.raws.selector.raw = fixedSelector | ||
if (hasFixed && fixedSelector) { | ||
if (!ruleNode.raws.selector) { | ||
ruleNode.selector = fixedSelector | ||
} else { | ||
ruleNode.raws.selector.raw = fixedSelector | ||
} | ||
} | ||
} | ||
/** | ||
* @param {string} message | ||
* @param {number} index | ||
*/ | ||
function complain (message, index) { | ||
report({ | ||
message, | ||
index, | ||
result, | ||
ruleName, | ||
node: ruleNode, | ||
}) | ||
} | ||
}) | ||
/** | ||
* @param {string} message | ||
* @param {number} index | ||
*/ | ||
function complain (message, index) { | ||
report({ | ||
message, | ||
index, | ||
result, | ||
ruleName, | ||
node: ruleNode, | ||
}) | ||
} | ||
}) | ||
} | ||
} | ||
@@ -157,2 +159,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import isStandardSyntaxRule from "../../utils/isStandardSyntaxRule.js" | ||
@@ -7,4 +9,2 @@ import isStandardSyntaxSelector from "../../utils/isStandardSyntaxSelector.js" | ||
import transformSelector from "../../utils/transformSelector.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -27,57 +27,59 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
root.walkRules((ruleNode) => { | ||
if (!isStandardSyntaxRule(ruleNode)) { | ||
if (!validOptions) { | ||
return | ||
} | ||
const selector = ruleNode.selector | ||
root.walkRules((ruleNode) => { | ||
if (!isStandardSyntaxRule(ruleNode)) { | ||
return | ||
} | ||
if (!selector.includes(`:`)) { | ||
return | ||
} | ||
const selector = ruleNode.selector | ||
transformSelector(result, ruleNode, (selectorTree) => { | ||
selectorTree.walkPseudos((pseudoNode) => { | ||
const pseudoElement = pseudoNode.value | ||
if (!selector.includes(`:`)) { | ||
return | ||
} | ||
if (!isStandardSyntaxSelector(pseudoElement)) { | ||
return | ||
} | ||
transformSelector(result, ruleNode, (selectorTree) => { | ||
selectorTree.walkPseudos((pseudoNode) => { | ||
const pseudoElement = pseudoNode.value | ||
if (!pseudoElement.includes(`::`) && !levelOneAndTwoPseudoElements.has(pseudoElement.toLowerCase().slice(1))) { | ||
return | ||
} | ||
if (!isStandardSyntaxSelector(pseudoElement)) { | ||
return | ||
} | ||
const expectedPseudoElement = primary === `lower` ? pseudoElement.toLowerCase() : pseudoElement.toUpperCase() | ||
if (!pseudoElement.includes(`::`) && !levelOneAndTwoPseudoElements.has(pseudoElement.toLowerCase().slice(1))) { | ||
return | ||
} | ||
if (pseudoElement === expectedPseudoElement) { | ||
return | ||
} | ||
const expectedPseudoElement = primary === `lower` ? pseudoElement.toLowerCase() : pseudoElement.toUpperCase() | ||
if (context.fix) { | ||
pseudoNode.value = expectedPseudoElement | ||
if (pseudoElement === expectedPseudoElement) { | ||
return | ||
} | ||
return | ||
} | ||
if (context.fix) { | ||
pseudoNode.value = expectedPseudoElement | ||
report({ | ||
message: messages.expected(pseudoElement, expectedPseudoElement), | ||
node: ruleNode, | ||
index: pseudoNode.sourceIndex, | ||
ruleName, | ||
result, | ||
return | ||
} | ||
report({ | ||
message: messages.expected(pseudoElement, expectedPseudoElement), | ||
node: ruleNode, | ||
index: pseudoNode.sourceIndex, | ||
ruleName, | ||
result, | ||
}) | ||
}) | ||
}) | ||
}) | ||
}) | ||
} | ||
} | ||
@@ -88,2 +90,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import valueParser from "postcss-value-parser" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import { isAtRule } from "../../utils/typeGuards.js" | ||
import isStandardSyntaxRule from "../../utils/isStandardSyntaxRule.js" | ||
import parseSelector from "../../utils/parseSelector.js" | ||
import { isBoolean, assertString } from "../../utils/validateTypes.js" | ||
import { isAtRule } from "../../utils/typeGuards.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import { assertString, isBoolean } from "../../utils/validateTypes.js" | ||
@@ -32,3 +32,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, secondaryOptions, context) => { | ||
function rule (primary, secondaryOptions, context) { | ||
const correctQuote = primary === `single` ? singleQuote : doubleQuote | ||
@@ -62,11 +62,11 @@ const erroneousQuote = primary === `single` ? doubleQuote : singleQuote | ||
switch (node.type) { | ||
case `atrule`: | ||
checkDeclOrAtRule(node, node.params, atRuleParamIndex) | ||
break | ||
case `decl`: | ||
checkDeclOrAtRule(node, node.value, declarationValueIndex) | ||
break | ||
case `rule`: | ||
checkRule(node) | ||
break | ||
case `atrule`: | ||
checkDeclOrAtRule(node, node.params, atRuleParamIndex) | ||
break | ||
case `decl`: | ||
checkDeclOrAtRule(node, node.value, declarationValueIndex) | ||
break | ||
case `rule`: | ||
checkRule(node) | ||
break | ||
} | ||
@@ -101,2 +101,3 @@ }) | ||
assertString(attributeNode.value) | ||
const needsCorrectEscape = attributeNode.value.includes(correctQuote) | ||
@@ -128,2 +129,3 @@ const needsOtherEscape = attributeNode.value.includes(erroneousQuote) | ||
assertString(attributeNode.value) | ||
const needsCorrectEscape = attributeNode.value.includes(correctQuote) | ||
@@ -196,5 +198,6 @@ const needsOtherEscape = attributeNode.value.includes(erroneousQuote) | ||
if (isAtRule(node) && node.name === `charset`) { | ||
// allow @charset rules to have double quotes, in spite of the configuration | ||
// TODO: @charset should always use double-quotes, see https://github.com/stylelint/stylelint/issues/2788 | ||
return | ||
const hasValidQuotes = node.params.startsWith(`"`) && node.params.endsWith(`"`) | ||
// pass through to the fixer only if the primary option is "double" | ||
if (hasValidQuotes || correctQuote === `'`) { return } | ||
} | ||
@@ -254,2 +257,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
@@ -21,32 +22,34 @@ import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}) | ||
function rule (primary) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`always`, `never`], | ||
}) | ||
if (!validOptions || !root.source || root.source.inline || root.source.lang === `object-literal` || root.document !== undefined /* Ignore HTML documents */) { | ||
return | ||
} | ||
if (!validOptions || !root.source || root.source.inline || root.source.lang === `object-literal` || root.document !== undefined /* Ignore HTML documents */) { | ||
return | ||
} | ||
const { hasBOM } = root.source.input | ||
const { hasBOM } = root.source.input | ||
if (primary === `always` && !hasBOM) { | ||
report({ | ||
result, | ||
ruleName, | ||
message: messages.expected, | ||
node: root, | ||
line: 1, | ||
}) | ||
} | ||
if (primary === `always` && !hasBOM) { | ||
report({ | ||
result, | ||
ruleName, | ||
message: messages.expected, | ||
node: root, | ||
line: 1, | ||
}) | ||
} | ||
if (primary === `never` && hasBOM) { | ||
report({ | ||
result, | ||
ruleName, | ||
message: messages.rejected, | ||
node: root, | ||
line: 1, | ||
}) | ||
if (primary === `never` && hasBOM) { | ||
report({ | ||
result, | ||
ruleName, | ||
message: messages.rejected, | ||
node: root, | ||
line: 1, | ||
}) | ||
} | ||
} | ||
@@ -58,2 +61,3 @@ } | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import valueParser from "postcss-value-parser" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import atRuleParamIndex from "../../utils/atRuleParamIndex.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import getDimension from "../../utils/getDimension.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -26,103 +26,105 @@ | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}) | ||
function rule (primary, _secondaryOptions, context) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { | ||
actual: primary, | ||
possible: [`lower`, `upper`], | ||
}) | ||
if (!validOptions) { | ||
return | ||
} | ||
if (!validOptions) { | ||
return | ||
} | ||
/** | ||
* @template {import('postcss').AtRule | import('postcss').Declaration} T | ||
* @param {T} node | ||
* @param {string} checkedValue | ||
* @param {(node: T) => number} getIndex | ||
* @returns {void} | ||
*/ | ||
function check (node, checkedValue, getIndex) { | ||
/** @type {Array<{ index: number, endIndex: number, message: string }>} */ | ||
const problems = [] | ||
/** | ||
* @param {import('postcss-value-parser').Node} valueNode | ||
* @returns {boolean} | ||
* @template {import('postcss').AtRule | import('postcss').Declaration} T | ||
* @param {T} node | ||
* @param {string} checkedValue | ||
* @param {(node: T) => number} getIndex | ||
* @returns {void} | ||
*/ | ||
function processValue (valueNode) { | ||
const { number, unit } = getDimension(valueNode) | ||
function check (node, checkedValue, getIndex) { | ||
/** @type {Array<{ index: number, endIndex: number, message: string }>} */ | ||
const problems = [] | ||
if (!number || !unit) {return false} | ||
/** | ||
* @param {import('postcss-value-parser').Node} valueNode | ||
* @returns {boolean} | ||
*/ | ||
function processValue (valueNode) { | ||
const { number, unit } = getDimension(valueNode) | ||
const expectedUnit = primary === `lower` ? unit.toLowerCase() : unit.toUpperCase() | ||
if (!number || !unit) { return false } | ||
if (unit === expectedUnit) { | ||
return false | ||
} | ||
const expectedUnit = primary === `lower` ? unit.toLowerCase() : unit.toUpperCase() | ||
const index = getIndex(node) | ||
if (unit === expectedUnit) { | ||
return false | ||
} | ||
problems.push({ | ||
index: index + valueNode.sourceIndex + number.length, | ||
endIndex: index + valueNode.sourceEndIndex, | ||
message: messages.expected(unit, expectedUnit), | ||
}) | ||
const index = getIndex(node) | ||
return true | ||
} | ||
problems.push({ | ||
index: index + valueNode.sourceIndex + number.length, | ||
endIndex: index + valueNode.sourceEndIndex, | ||
message: messages.expected(unit, expectedUnit), | ||
}) | ||
const parsedValue = valueParser(checkedValue).walk((valueNode) => { | ||
// Ignore wrong units within `url` function | ||
let needFix = false | ||
const value = valueNode.value | ||
if (valueNode.type === `function` && value.toLowerCase() === `url`) { | ||
return false | ||
return true | ||
} | ||
if (value.includes(`*`)) { | ||
value.split(`*`).some((val) => processValue({ | ||
...valueNode, | ||
sourceIndex: value.indexOf(val) + val.length + 1, | ||
value: val, | ||
})) | ||
} | ||
const parsedValue = valueParser(checkedValue).walk((valueNode) => { | ||
// Ignore wrong units within `url` function | ||
let needFix = false | ||
const value = valueNode.value | ||
needFix = processValue(valueNode) | ||
if (valueNode.type === `function` && value.toLowerCase() === `url`) { | ||
return false | ||
} | ||
if (needFix && context.fix) { | ||
valueNode.value = primary === `lower` ? value.toLowerCase() : value.toUpperCase() | ||
} | ||
}) | ||
if (value.includes(`*`)) { | ||
value.split(`*`).some((val) => processValue({ | ||
...valueNode, | ||
sourceIndex: value.indexOf(val) + val.length + 1, | ||
value: val, | ||
})) | ||
} | ||
if (problems.length) { | ||
if (context.fix) { | ||
if (`name` in node && node.name === `media`) { | ||
node.params = parsedValue.toString() | ||
} else if (`value` in node) { | ||
node.value = parsedValue.toString() | ||
needFix = processValue(valueNode) | ||
if (needFix && context.fix) { | ||
valueNode.value = primary === `lower` ? value.toLowerCase() : value.toUpperCase() | ||
} | ||
} else { | ||
for (const err of problems) { | ||
report({ | ||
index: err.index, | ||
endIndex: err.endIndex, | ||
message: err.message, | ||
node, | ||
result, | ||
ruleName, | ||
}) | ||
}) | ||
if (problems.length) { | ||
if (context.fix) { | ||
if (`name` in node && node.name === `media`) { | ||
node.params = parsedValue.toString() | ||
} else if (`value` in node) { | ||
node.value = parsedValue.toString() | ||
} | ||
} else { | ||
for (const err of problems) { | ||
report({ | ||
index: err.index, | ||
endIndex: err.endIndex, | ||
message: err.message, | ||
node, | ||
result, | ||
ruleName, | ||
}) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
root.walkAtRules((atRule) => { | ||
if (!/^media$/i.test(atRule.name) && !(`variable` in atRule)) { | ||
return | ||
} | ||
root.walkAtRules((atRule) => { | ||
if (!(/^media$/i).test(atRule.name) && !(`variable` in atRule)) { | ||
return | ||
} | ||
check(atRule, atRule.params, atRuleParamIndex) | ||
}) | ||
root.walkDecls((decl) => check(decl, decl.value, declarationValueIndex)) | ||
check(atRule, atRule.params, atRuleParamIndex) | ||
}) | ||
root.walkDecls((decl) => check(decl, decl.value, declarationValueIndex)) | ||
} | ||
} | ||
@@ -133,2 +135,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import getDeclarationValue from "../../utils/getDeclarationValue.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import setDeclarationValue from "../../utils/setDeclarationValue.js" | ||
import valueListCommaWhitespaceChecker from "../../utils/valueListCommaWhitespaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -29,3 +29,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -51,17 +51,20 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (declNode, index) => { | ||
const valueIndex = declarationValueIndex(declNode) | ||
fix: context.fix | ||
? (declNode, index) => { | ||
const valueIndex = declarationValueIndex(declNode) | ||
if (index <= valueIndex) { | ||
return false | ||
} | ||
if (index <= valueIndex) { | ||
return false | ||
} | ||
fixData = fixData || new Map() | ||
const commaIndices = fixData.get(declNode) || [] | ||
fixData = fixData || (new Map) | ||
commaIndices.push(index) | ||
fixData.set(declNode, commaIndices) | ||
const commaIndices = fixData.get(declNode) || [] | ||
return true | ||
} : null, | ||
commaIndices.push(index) | ||
fixData.set(declNode, commaIndices) | ||
return true | ||
} | ||
: null, | ||
determineIndex: (declString, match) => { | ||
@@ -72,3 +75,3 @@ const nextChars = declString.substring(match.endIndex, declString.length) | ||
// ending the comment so we're fine | ||
if (/^[ \t]*\/\//.test(nextChars)) { | ||
if ((/^[ \t]*\/\//).test(nextChars)) { | ||
return false | ||
@@ -78,3 +81,3 @@ } | ||
// If there are spaces and then a comment begins, look for the newline | ||
return /^[ \t]*\/\*/.test(nextChars) ? declString.indexOf(`*/`, match.endIndex) + 1 : match.startIndex | ||
return (/^[ \t]*\/\*/).test(nextChars) ? declString.indexOf(`*/`, match.endIndex) + 1 : match.startIndex | ||
}, | ||
@@ -107,2 +110,3 @@ }) | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import valueListCommaWhitespaceChecker from "../../utils/valueListCommaWhitespaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -25,3 +25,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary) => { | ||
function rule (primary) { | ||
const checker = whitespaceChecker(`newline`, primary, messages) | ||
@@ -51,2 +51,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import getDeclarationValue from "../../utils/getDeclarationValue.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import setDeclarationValue from "../../utils/setDeclarationValue.js" | ||
import valueListCommaWhitespaceChecker from "../../utils/valueListCommaWhitespaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -30,3 +30,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -52,17 +52,20 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (declNode, index) => { | ||
const valueIndex = declarationValueIndex(declNode) | ||
fix: context.fix | ||
? (declNode, index) => { | ||
const valueIndex = declarationValueIndex(declNode) | ||
if (index <= valueIndex) { | ||
return false | ||
} | ||
if (index <= valueIndex) { | ||
return false | ||
} | ||
fixData = fixData || new Map() | ||
const commaIndices = fixData.get(declNode) || [] | ||
fixData = fixData || (new Map) | ||
commaIndices.push(index) | ||
fixData.set(declNode, commaIndices) | ||
const commaIndices = fixData.get(declNode) || [] | ||
return true | ||
} : null, | ||
commaIndices.push(index) | ||
fixData.set(declNode, commaIndices) | ||
return true | ||
} | ||
: null, | ||
}) | ||
@@ -94,2 +97,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import declarationValueIndex from "../../utils/declarationValueIndex.js" | ||
import getDeclarationValue from "../../utils/getDeclarationValue.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import setDeclarationValue from "../../utils/setDeclarationValue.js" | ||
import valueListCommaWhitespaceChecker from "../../utils/valueListCommaWhitespaceChecker.js" | ||
import whitespaceChecker from "../../utils/whitespaceChecker.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -30,3 +30,3 @@ const { utils: { ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const checker = whitespaceChecker(`space`, primary, messages) | ||
@@ -52,17 +52,20 @@ | ||
checkedRuleName: ruleName, | ||
fix: context.fix ? (declNode, index) => { | ||
const valueIndex = declarationValueIndex(declNode) | ||
fix: context.fix | ||
? (declNode, index) => { | ||
const valueIndex = declarationValueIndex(declNode) | ||
if (index <= valueIndex) { | ||
return false | ||
} | ||
if (index <= valueIndex) { | ||
return false | ||
} | ||
fixData = fixData || new Map() | ||
const commaIndices = fixData.get(declNode) || [] | ||
fixData = fixData || (new Map) | ||
commaIndices.push(index) | ||
fixData.set(declNode, commaIndices) | ||
const commaIndices = fixData.get(declNode) || [] | ||
return true | ||
} : null, | ||
commaIndices.push(index) | ||
fixData.set(declNode, commaIndices) | ||
return true | ||
} | ||
: null, | ||
}) | ||
@@ -94,2 +97,3 @@ | ||
rule.meta = meta | ||
export default rule |
import stylelint from "stylelint" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import getDeclarationValue from "../../utils/getDeclarationValue.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
import { isNumber } from "../../utils/validateTypes.js" | ||
import setDeclarationValue from "../../utils/setDeclarationValue.js" | ||
import { isNumber } from "../../utils/validateTypes.js" | ||
import { addNamespace } from "../../utils/addNamespace.js" | ||
import { getRuleDocUrl } from "../../utils/getRuleDocUrl.js" | ||
@@ -25,3 +25,3 @@ const { utils: { report, ruleMessages, validateOptions } } = stylelint | ||
/** @type {import('stylelint').Rule} */ | ||
const rule = (primary, _secondaryOptions, context) => { | ||
function rule (primary, _secondaryOptions, context) { | ||
const maxAdjacentNewlines = primary + 1 | ||
@@ -69,2 +69,3 @@ | ||
rule.meta = meta | ||
export default rule |
@@ -19,3 +19,3 @@ /** | ||
if (!/\r?\n/.test(after)) { | ||
if (!(/\r?\n/).test(after)) { | ||
raws.after += newline.repeat(2) | ||
@@ -22,0 +22,0 @@ } else { |
@@ -1,4 +0,4 @@ | ||
import { TokenType, isToken, stringify, tokenize } from "@csstools/css-tokenizer" | ||
import { isTokenNode, parseCommaSeparatedListOfComponentValues, isSimpleBlockNode } from "@csstools/css-parser-algorithms" | ||
import { isGeneralEnclosed, isMediaFeature, isMediaQueryInvalid, parseFromTokens } from "@csstools/media-query-list-parser" | ||
import { isSimpleBlockNode, isTokenNode, parseCommaSeparatedListOfComponentValues } from "@csstools/css-parser-algorithms" | ||
import { isToken, stringify, tokenize, TokenType } from "@csstools/css-tokenizer" | ||
@@ -30,3 +30,3 @@ /** @typedef {Array<import('@csstools/media-query-list-parser').MediaQuery>} MediaQueryList */ | ||
if ( | ||
!isSimpleBlockNode(componentValue) || componentValue.startToken[0] !== TokenType.OpenParen | ||
!isSimpleBlockNode(componentValue) || componentValue.startToken[0] !== TokenType.OpenParen | ||
) { | ||
@@ -50,3 +50,3 @@ return [] | ||
if (token[0] !== TokenType.Ident) {return} | ||
if (token[0] !== TokenType.Ident) { return } | ||
@@ -53,0 +53,0 @@ callback(token) |
@@ -65,3 +65,3 @@ import stylelint from "stylelint" | ||
*/ | ||
const getCommaCheckIndex = (commaNode, nodeIndex) => { | ||
function getCommaCheckIndex (commaNode, nodeIndex) { | ||
let commaBefore = valueNode.before + argumentStrings.slice(0, nodeIndex).join(``) + commaNode.before | ||
@@ -68,0 +68,0 @@ |
@@ -10,5 +10,5 @@ const HAS_EMPTY_LINE = /\n[\r\t ]*\n/ | ||
export default function hasEmptyLine (string) { | ||
if (string === `` || string === undefined) {return false} | ||
if (string === `` || string === undefined) { return false } | ||
return HAS_EMPTY_LINE.test(string) | ||
} |
@@ -9,3 +9,3 @@ /** | ||
export default function isSingleLineString (input) { | ||
return !/[\n\r]/.test(input) | ||
return !(/[\n\r]/).test(input) | ||
} |
@@ -8,7 +8,7 @@ /** | ||
// inline comments, while the Less parser uses `inline`. | ||
if (`inline` in comment) {return false} | ||
if (`inline` in comment) { return false } | ||
if (`inline` in comment.raws) {return false} | ||
if (`inline` in comment.raws) { return false } | ||
return true | ||
} |
@@ -0,3 +1,3 @@ | ||
import { isRule } from "./typeGuards.js" | ||
import isScssVariable from "./isScssVariable.js" | ||
import { isRule } from "./typeGuards.js" | ||
@@ -4,0 +4,0 @@ /** |
@@ -26,3 +26,3 @@ import hasInterpolation from "./hasInterpolation.js" | ||
// Less :extend() | ||
if (/:extend(?:\(.*?\))?/.test(selector)) { | ||
if ((/:extend(?:\(.*?\))?/).test(selector)) { | ||
return false | ||
@@ -32,3 +32,3 @@ } | ||
// Less mixin with resolved nested selectors (e.g. .foo().bar or .foo(@a, @b)[bar]) | ||
if (/\.[\w-]+\(.*\).+/.test(selector)) { | ||
if ((/\.[\w-]+\(.*\).+/).test(selector)) { | ||
return false | ||
@@ -43,3 +43,3 @@ } | ||
// Less Parametric mixins (e.g. .mixin(@variable: x) {}) | ||
if (/\(@.*\)$/.test(selector)) { | ||
if ((/\(@.*\)$/).test(selector)) { | ||
return false | ||
@@ -46,0 +46,0 @@ } |
@@ -13,3 +13,3 @@ import hasInterpolation from "./hasInterpolation.js" | ||
// Ignore operators before variables (example -$variable) | ||
if (/^[-+*/]/.test(value.charAt(0))) { | ||
if ((/^[-+*/]/).test(value.charAt(0))) { | ||
normalizedValue = normalizedValue.slice(1) | ||
@@ -24,3 +24,3 @@ } | ||
// SCSS namespace (example namespace.$variable) | ||
if (/^.+\.\$/.test(value)) { | ||
if ((/^.+\.\$/).test(value)) { | ||
return false | ||
@@ -30,3 +30,3 @@ } | ||
// SCSS namespace (example namespace.function-name()) | ||
if (/^.+\.[-\w]+\(/.test(value)) { | ||
if ((/^.+\.[-\w]+\(/).test(value)) { | ||
return false | ||
@@ -48,3 +48,3 @@ } | ||
// and https://github.com/stylelint/stylelint/issues/4707 | ||
if (/__MSG_\S+__/.test(value)) { | ||
if ((/__MSG_\S+__/).test(value)) { | ||
return false | ||
@@ -51,0 +51,0 @@ } |
import stylelint from "stylelint" | ||
import styleSearch from "style-search" | ||
import { assertString } from "./validateTypes.js" | ||
import atRuleParamIndex from "./atRuleParamIndex.js" | ||
import { assertString } from "./validateTypes.js" | ||
@@ -30,3 +30,3 @@ const { utils: { report } } = stylelint | ||
while ((execResult = /^[^\S\r\n]*\/\*([\s\S]*?)\*\//.exec(params.slice(index + 1)))) { | ||
while ((execResult = (/^[^\S\r\n]*\/\*([\s\S]*?)\*\//).exec(params.slice(index + 1)))) { | ||
assertString(execResult[0]) | ||
@@ -36,3 +36,3 @@ index += execResult[0].length | ||
if ((execResult = /^([^\S\r\n]*\/\/[\s\S]*?)\r?\n/.exec(params.slice(index + 1)))) { | ||
if ((execResult = (/^([^\S\r\n]*\/\/[\s\S]*?)\r?\n/).exec(params.slice(index + 1)))) { | ||
assertString(execResult[1]) | ||
@@ -39,0 +39,0 @@ index += execResult[1].length |
@@ -11,3 +11,3 @@ /** @typedef {import('postcss').Node} PostcssNode */ | ||
export default function nextNonCommentNode (startNode) { | ||
if (!startNode || !startNode.next) {return null} | ||
if (!startNode || !startNode.next) { return null } | ||
@@ -14,0 +14,0 @@ if (startNode.type === `comment`) { |
@@ -31,2 +31,3 @@ import stylelint from "stylelint" | ||
hasFixed = false | ||
const selector = rule.raws.selector ? rule.raws.selector.raw : rule.selector | ||
@@ -42,3 +43,3 @@ | ||
// Ignore spaced descendant combinator | ||
if (/\s/.test(node.value)) { | ||
if ((/\s/).test(node.value)) { | ||
return | ||
@@ -45,0 +46,0 @@ } |
@@ -110,38 +110,38 @@ import configurationError from "./configurationError.js" | ||
switch (expectation) { | ||
case `always`: | ||
expectBefore() | ||
break | ||
case `never`: | ||
rejectBefore() | ||
break | ||
case `always-single-line`: | ||
if (!isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
case `always`: | ||
expectBefore() | ||
break | ||
case `never`: | ||
rejectBefore() | ||
break | ||
case `always-single-line`: | ||
if (!isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
expectBefore(messages.expectedBeforeSingleLine) | ||
break | ||
case `never-single-line`: | ||
if (!isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
expectBefore(messages.expectedBeforeSingleLine) | ||
break | ||
case `never-single-line`: | ||
if (!isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
rejectBefore(messages.rejectedBeforeSingleLine) | ||
break | ||
case `always-multi-line`: | ||
if (isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
rejectBefore(messages.rejectedBeforeSingleLine) | ||
break | ||
case `always-multi-line`: | ||
if (isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
expectBefore(messages.expectedBeforeMultiLine) | ||
break | ||
case `never-multi-line`: | ||
if (isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
expectBefore(messages.expectedBeforeMultiLine) | ||
break | ||
case `never-multi-line`: | ||
if (isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
rejectBefore(messages.rejectedBeforeMultiLine) | ||
break | ||
default: | ||
throw configurationError(`Unknown expectation "${expectation}"`) | ||
rejectBefore(messages.rejectedBeforeMultiLine) | ||
break | ||
default: | ||
throw configurationError(`Unknown expectation "${expectation}"`) | ||
} | ||
@@ -158,38 +158,38 @@ } | ||
switch (expectation) { | ||
case `always`: | ||
expectAfter() | ||
break | ||
case `never`: | ||
rejectAfter() | ||
break | ||
case `always-single-line`: | ||
if (!isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
case `always`: | ||
expectAfter() | ||
break | ||
case `never`: | ||
rejectAfter() | ||
break | ||
case `always-single-line`: | ||
if (!isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
expectAfter(messages.expectedAfterSingleLine) | ||
break | ||
case `never-single-line`: | ||
if (!isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
expectAfter(messages.expectedAfterSingleLine) | ||
break | ||
case `never-single-line`: | ||
if (!isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
rejectAfter(messages.rejectedAfterSingleLine) | ||
break | ||
case `always-multi-line`: | ||
if (isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
rejectAfter(messages.rejectedAfterSingleLine) | ||
break | ||
case `always-multi-line`: | ||
if (isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
expectAfter(messages.expectedAfterMultiLine) | ||
break | ||
case `never-multi-line`: | ||
if (isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
expectAfter(messages.expectedAfterMultiLine) | ||
break | ||
case `never-multi-line`: | ||
if (isSingleLineString(lineCheckStr || source)) { | ||
return | ||
} | ||
rejectAfter(messages.rejectedAfterMultiLine) | ||
break | ||
default: | ||
throw configurationError(`Unknown expectation "${expectation}"`) | ||
rejectAfter(messages.rejectedAfterMultiLine) | ||
break | ||
default: | ||
throw configurationError(`Unknown expectation "${expectation}"`) | ||
} | ||
@@ -196,0 +196,0 @@ } |
{ | ||
"name": "@stylistic/stylelint-plugin", | ||
"description": "A collection of stylistic/formatting Stylelint rules", | ||
"version": "2.1.1", | ||
"version": "2.1.2", | ||
"type": "module", | ||
@@ -12,10 +12,10 @@ "exports": "./lib/index.js", | ||
"dependencies": { | ||
"@csstools/css-parser-algorithms": "^2.5.0", | ||
"@csstools/css-tokenizer": "^2.2.3", | ||
"@csstools/media-query-list-parser": "^2.1.7", | ||
"@csstools/css-parser-algorithms": "^2.6.1", | ||
"@csstools/css-tokenizer": "^2.2.4", | ||
"@csstools/media-query-list-parser": "^2.1.9", | ||
"is-plain-object": "^5.0.0", | ||
"postcss-selector-parser": "^6.0.15", | ||
"postcss-selector-parser": "^6.0.16", | ||
"postcss-value-parser": "^4.2.0", | ||
"style-search": "^0.1.0", | ||
"stylelint": "^16.2.1" | ||
"stylelint": "^16.4.0" | ||
}, | ||
@@ -53,7 +53,11 @@ "peerDependencies": { | ||
"devDependencies": { | ||
"@eslint/js": "^9.1.1", | ||
"@firefoxic/utils": "^0.1.0", | ||
"@stylistic/eslint-plugin-js": "^1.7.2", | ||
"@synap-ac/node-dot-extra-reporter": "^1.1.0", | ||
"common-tags": "^1.8.2", | ||
"eslint": "^8.56.0", | ||
"eslint": "^9.1.1", | ||
"globals": "^15.1.0", | ||
"husky": "^9.0.11", | ||
"postcss": "^8.4.35", | ||
"postcss": "^8.4.38", | ||
"postcss-html": "^1.6.0", | ||
@@ -65,4 +69,4 @@ "postcss-less": "^6.0.0", | ||
"scripts": { | ||
"lint": "eslint . --ignore-path .gitignore", | ||
"pretest": "pnpm lint", | ||
"lint": "eslint", | ||
"pretest": "pnpm lint --cache", | ||
"test": "node --test --test-reporter @synap-ac/node-dot-extra-reporter", | ||
@@ -72,4 +76,5 @@ "test:coverage": "node --test --experimental-test-coverage", | ||
"preversion": "pnpm test", | ||
"version": "update-changelog && git add CHANGELOG.md", | ||
"postversion": "pnpm publish --access=public" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
321270
9703
13