first commit
This commit is contained in:
+268
@@ -0,0 +1,268 @@
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @import { CSSRuleDefinition } from "../types.js"
|
||||
* @typedef {"notLogicalProperty" | "notLogicalValue" | "notLogicalUnit" | "replaceWithLogicalProperty" | "replaceWithLogicalValue" | "replaceWithLogicalUnit"} PreferLogicalPropertiesMessageIds
|
||||
* @typedef {[{
|
||||
* allowProperties?: string[],
|
||||
* allowUnits?: string[]
|
||||
* }]} PreferLogicalPropertiesOptions
|
||||
* @typedef {CSSRuleDefinition<{ RuleOptions: PreferLogicalPropertiesOptions, MessageIds: PreferLogicalPropertiesMessageIds }>} PreferLogicalPropertiesRuleDefinition
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
const propertiesReplacements = new Map([
|
||||
["bottom", "inset-block-end"],
|
||||
["border-bottom", "border-block-end"],
|
||||
["border-bottom-color", "border-block-end-color"],
|
||||
["border-bottom-left-radius", "border-end-start-radius"],
|
||||
["border-bottom-right-radius", "border-end-end-radius"],
|
||||
["border-bottom-style", "border-block-end-style"],
|
||||
["border-bottom-width", "border-block-end-width"],
|
||||
["border-left", "border-inline-start"],
|
||||
["border-left-color", "border-inline-start-color"],
|
||||
["border-left-style", "border-inline-start-style"],
|
||||
["border-left-width", "border-inline-start-width"],
|
||||
["border-right", "border-inline-end"],
|
||||
["border-right-color", "border-inline-end-color"],
|
||||
["border-right-style", "border-inline-end-style"],
|
||||
["border-right-width", "border-inline-end-width"],
|
||||
["border-top", "border-block-start"],
|
||||
["border-top-color", "border-block-start-color"],
|
||||
["border-top-left-radius", "border-start-start-radius"],
|
||||
["border-top-right-radius", "border-start-end-radius"],
|
||||
["border-top-style", "border-block-start-style"],
|
||||
["border-top-width", "border-block-start-width"],
|
||||
["contain-intrinsic-height", "contain-intrinsic-block-size"],
|
||||
["contain-intrinsic-width", "contain-intrinsic-inline-size"],
|
||||
["height", "block-size"],
|
||||
["left", "inset-inline-start"],
|
||||
["margin-bottom", "margin-block-end"],
|
||||
["margin-left", "margin-inline-start"],
|
||||
["margin-right", "margin-inline-end"],
|
||||
["margin-top", "margin-block-start"],
|
||||
["max-height", "max-block-size"],
|
||||
["max-width", "max-inline-size"],
|
||||
["min-height", "min-block-size"],
|
||||
["min-width", "min-inline-size"],
|
||||
["overflow-x", "overflow-inline"],
|
||||
["overflow-y", "overflow-block"],
|
||||
["overscroll-behavior-x", "overscroll-behavior-inline"],
|
||||
["overscroll-behavior-y", "overscroll-behavior-block"],
|
||||
["padding-bottom", "padding-block-end"],
|
||||
["padding-left", "padding-inline-start"],
|
||||
["padding-right", "padding-inline-end"],
|
||||
["padding-top", "padding-block-start"],
|
||||
["right", "inset-inline-end"],
|
||||
["scroll-margin-bottom", "scroll-margin-block-end"],
|
||||
["scroll-margin-left", "scroll-margin-inline-start"],
|
||||
["scroll-margin-right", "scroll-margin-inline-end"],
|
||||
["scroll-margin-top", "scroll-margin-block-start"],
|
||||
["scroll-padding-bottom", "scroll-padding-block-end"],
|
||||
["scroll-padding-left", "scroll-padding-inline-start"],
|
||||
["scroll-padding-right", "scroll-padding-inline-end"],
|
||||
["scroll-padding-top", "scroll-padding-block-start"],
|
||||
["top", "inset-block-start"],
|
||||
["width", "inline-size"],
|
||||
]);
|
||||
const propertyValuesReplacements = new Map([
|
||||
[
|
||||
"text-align",
|
||||
{
|
||||
left: "start",
|
||||
right: "end",
|
||||
},
|
||||
],
|
||||
[
|
||||
"resize",
|
||||
{
|
||||
horizontal: "inline",
|
||||
vertical: "block",
|
||||
},
|
||||
],
|
||||
[
|
||||
"caption-side",
|
||||
{
|
||||
left: "inline-start",
|
||||
right: "inline-end",
|
||||
},
|
||||
],
|
||||
[
|
||||
"box-orient",
|
||||
{
|
||||
horizontal: "inline-axis",
|
||||
vertical: "block-axis",
|
||||
},
|
||||
],
|
||||
[
|
||||
"float",
|
||||
{
|
||||
left: "inline-start",
|
||||
right: "inline-end",
|
||||
},
|
||||
],
|
||||
[
|
||||
"clear",
|
||||
{
|
||||
left: "inline-start",
|
||||
right: "inline-end",
|
||||
},
|
||||
],
|
||||
]);
|
||||
const unitReplacements = new Map([
|
||||
["cqh", "cqb"],
|
||||
["cqw", "cqi"],
|
||||
["dvh", "dvb"],
|
||||
["dvw", "dvi"],
|
||||
["lvh", "lvb"],
|
||||
["lvw", "lvi"],
|
||||
["svh", "svb"],
|
||||
["svw", "svi"],
|
||||
["vh", "vb"],
|
||||
["vw", "vi"],
|
||||
]);
|
||||
//-----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//-----------------------------------------------------------------------------
|
||||
export default /** @satisfies {PreferLogicalPropertiesRuleDefinition} */ ({
|
||||
meta: {
|
||||
type: "problem",
|
||||
hasSuggestions: true,
|
||||
docs: {
|
||||
description: "Enforce the use of logical properties",
|
||||
url: "https://github.com/eslint/css/blob/main/docs/rules/prefer-logical-properties.md",
|
||||
},
|
||||
schema: [
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
allowProperties: {
|
||||
type: "array",
|
||||
items: {
|
||||
enum: Array.from(propertiesReplacements.keys()),
|
||||
},
|
||||
uniqueItems: true,
|
||||
},
|
||||
allowUnits: {
|
||||
type: "array",
|
||||
items: {
|
||||
enum: Array.from(unitReplacements.keys()),
|
||||
},
|
||||
uniqueItems: true,
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
},
|
||||
],
|
||||
defaultOptions: [
|
||||
{
|
||||
allowProperties: [],
|
||||
allowUnits: [],
|
||||
},
|
||||
],
|
||||
messages: {
|
||||
notLogicalProperty: "Expected logical property '{{replacement}}' instead of '{{property}}'.",
|
||||
notLogicalValue: "Expected logical value '{{replacement}}' instead of '{{value}}'.",
|
||||
notLogicalUnit: "Expected logical unit '{{replacement}}' instead of '{{unit}}'.",
|
||||
replaceWithLogicalProperty: "Replace '{{property}}' with logical property '{{replacement}}'.",
|
||||
replaceWithLogicalValue: "Replace '{{value}}' with logical value '{{replacement}}'.",
|
||||
replaceWithLogicalUnit: "Replace '{{unit}}' with logical unit '{{replacement}}'.",
|
||||
},
|
||||
},
|
||||
create(context) {
|
||||
const [{ allowProperties, allowUnits }] = context.options;
|
||||
return {
|
||||
Declaration(node) {
|
||||
const parent = context.sourceCode.getParent(node);
|
||||
if (parent.type === "SupportsDeclaration") {
|
||||
return;
|
||||
}
|
||||
const propertyReplacement = propertiesReplacements.get(node.property);
|
||||
if (propertyReplacement &&
|
||||
!allowProperties.includes(node.property)) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: "notLogicalProperty",
|
||||
data: {
|
||||
property: node.property,
|
||||
replacement: propertyReplacement,
|
||||
},
|
||||
suggest: [
|
||||
{
|
||||
messageId: "replaceWithLogicalProperty",
|
||||
data: {
|
||||
property: node.property,
|
||||
replacement: propertyReplacement,
|
||||
},
|
||||
fix(fixer) {
|
||||
return fixer.replaceTextRange([
|
||||
node.loc.start.offset,
|
||||
node.loc.start.offset +
|
||||
node.property.length,
|
||||
], propertyReplacement);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
const valueReplacements = propertyValuesReplacements.get(node.property);
|
||||
if (valueReplacements &&
|
||||
node.value.type === "Value" &&
|
||||
node.value.children[0].type === "Identifier") {
|
||||
const identifier = node.value.children[0];
|
||||
const nodeValue = identifier.name;
|
||||
const valueReplacement = valueReplacements[nodeValue];
|
||||
if (valueReplacement) {
|
||||
context.report({
|
||||
loc: identifier.loc,
|
||||
messageId: "notLogicalValue",
|
||||
data: {
|
||||
value: nodeValue,
|
||||
replacement: valueReplacement,
|
||||
},
|
||||
suggest: [
|
||||
{
|
||||
messageId: "replaceWithLogicalValue",
|
||||
data: {
|
||||
value: nodeValue,
|
||||
replacement: valueReplacement,
|
||||
},
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(identifier, valueReplacement);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
Dimension(node) {
|
||||
const unitReplacement = unitReplacements.get(node.unit);
|
||||
if (unitReplacement && !allowUnits.includes(node.unit)) {
|
||||
context.report({
|
||||
loc: node.loc,
|
||||
messageId: "notLogicalUnit",
|
||||
data: {
|
||||
unit: node.unit,
|
||||
replacement: unitReplacement,
|
||||
},
|
||||
suggest: [
|
||||
{
|
||||
messageId: "replaceWithLogicalUnit",
|
||||
data: {
|
||||
unit: node.unit,
|
||||
replacement: unitReplacement,
|
||||
},
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(node, node.value + unitReplacement);
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user