stylelint
Version:
A mighty CSS linter that helps you avoid errors and enforce conventions.
156 lines (126 loc) • 4.16 kB
JavaScript
import valueParser from 'postcss-value-parser';
import { flowRelativeUnits, physicalToFlowRelativeUnits } from '../../reference/units.mjs';
import { isRegExp, isString } from '../../utils/validateTypes.mjs';
import { declarationValueIndex } from '../../utils/nodeFieldIndices.mjs';
import getDeclarationValue from '../../utils/getDeclarationValue.mjs';
import getDimension from '../../utils/getDimension.mjs';
import isStandardSyntaxDeclaration from '../../utils/isStandardSyntaxDeclaration.mjs';
import { mayIncludeRegexes } from '../../utils/regexes.mjs';
import optionsMatches from '../../utils/optionsMatches.mjs';
import report from '../../utils/report.mjs';
import ruleMessages from '../../utils/ruleMessages.mjs';
import setDeclarationValue from '../../utils/setDeclarationValue.mjs';
import validateOptions from '../../utils/validateOptions.mjs';
const ruleName = 'unit-layout-mappings';
const messages = ruleMessages(ruleName, {
rejected: (type, unit) => `Disallowed ${type} unit "${unit}"`,
expected: (actual, expected) => `Expected "${actual}" to be "${expected}"`,
});
const meta = {
url: 'https://stylelint.io/user-guide/rules/unit-layout-mappings',
fixable: true,
};
/** @import {Directionality} from 'stylelint' */
/**
* Builds a physical → flow-relative unit map based on the inline direction.
*
* @param {Directionality} inline
* @returns {Map<string, string>}
*/
function buildDirectionalMap(inline) {
const key =
inline === 'left-to-right' || inline === 'right-to-left'
? 'horizontalInline'
: 'verticalInline';
return new Map(
[...physicalToFlowRelativeUnits].map(([physical, mapping]) => [physical, mapping[key]]),
);
}
/** @type {import('stylelint').CoreRules[ruleName]} */
const rule = (primary, secondaryOptions) => {
return (root, result) => {
const validOptions = validateOptions(
result,
ruleName,
{
actual: primary,
possible: ['flow-relative', 'physical'],
},
{
actual: secondaryOptions,
possible: {
ignoreUnits: [isString, isRegExp],
},
optional: true,
},
);
if (!validOptions) return;
const directionality = result.stylelint.config?.languageOptions?.directionality;
const inline = directionality?.inline;
const directionalMap = inline ? buildDirectionalMap(inline) : null;
/**
* @param {string} type
* @param {string} unit
* @param {number} index
* @param {number} endIndex
* @param {import('postcss').Declaration} decl
*/
function complain(type, unit, index, endIndex, decl) {
report({
index,
endIndex,
message: messages.rejected,
messageArgs: [type, unit],
node: decl,
result,
ruleName,
});
}
root.walkDecls((decl) => {
if (!mayIncludeRegexes.axisUnit.test(decl.value)) return;
if (!isStandardSyntaxDeclaration(decl)) return;
const parsedValue = valueParser(getDeclarationValue(decl));
const valueIndex = declarationValueIndex(decl);
parsedValue.walk((valueNode) => {
const { number, unit } = getDimension(valueNode);
if (!number || !unit) return;
if (optionsMatches(secondaryOptions, 'ignoreUnits', unit)) return;
const { sourceIndex, sourceEndIndex } = valueNode;
const index = valueIndex + sourceIndex + number.length;
const endIndex = valueIndex + sourceEndIndex;
if (primary === 'physical') {
if (!flowRelativeUnits.has(unit)) return;
complain('flow-relative', unit, index, endIndex, decl);
return;
}
if (!physicalToFlowRelativeUnits.has(unit)) return;
if (!directionalMap) {
complain('physical', unit, index, endIndex, decl);
return;
}
const expectedUnit = directionalMap.get(unit);
if (!expectedUnit) return;
report({
index,
endIndex,
message: messages.expected,
messageArgs: [unit, expectedUnit],
node: decl,
result,
ruleName,
fix: {
apply: () => {
valueNode.value = number + expectedUnit;
setDeclarationValue(decl, parsedValue.toString());
},
node: decl,
},
});
});
});
};
};
rule.ruleName = ruleName;
rule.messages = messages;
rule.meta = meta;
export default rule;