Skip to content

Commit

Permalink
feat: implement tagChildrenIndent options in indent (#248)
Browse files Browse the repository at this point in the history
* feat: implement tagChildrenIndent options in indent

* fix

* Update indent.md

* add tests
  • Loading branch information
yeonjuan authored Dec 14, 2024
1 parent fb97ff0 commit 22325ef
Show file tree
Hide file tree
Showing 6 changed files with 249 additions and 70 deletions.
9 changes: 8 additions & 1 deletion docs/rules/indent.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,17 @@ This rule has an object option:
"error",
2,
{
"Attribute": 2
"Attribute": 2,
"tagChildrenIndent": {
"html": 0,
"div": 1
// ...
}
}
]
}
```

- `Attribute` (default: 1): enforces indentation level for attributes. e.g. indent of 2 spaces with `Attribute` set to `2` will indent the attributes with `4` spaces (2 x 2).

- `tagChildrenIndent` (default: `{}`): specifies the indent increment of the child tags of the specified tag. e.g. For example, `"tagChildIndent": { "html": 0 }` will set the `<html/>` tag children to 0 indent (2 x 0).
178 changes: 113 additions & 65 deletions packages/eslint-plugin/lib/rules/indent/indent.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
* @typedef { import("../../types").RuleModule } RuleModule
* @typedef { import("../../types").AnyNode } AnyNode
* @typedef { import("../../types").Line } Line
* @typedef { import("../../types").Tag } Tag
* @typedef { import("../../types").RuleListener } RuleListener
* @typedef { import("../../types").Context } Context
* @typedef { import("eslint").AST.Token } Token
* @typedef { import("eslint").SourceCode } SourceCode
* @typedef { import("eslint").AST.Range } Range
Expand All @@ -15,11 +17,15 @@
* @property {"space"} SPACE
* @typedef {Object} MessageId
* @property {"wrongIndent"} WRONG_INDENT
* @typedef {Object} IndentOptionInfo
* @property {IndentType["TAB"] | IndentType["SPACE"]} indentType
* @property {number} indentSize
* @property {string} indentChar
*/

const { parse } = require("@html-eslint/template-parser");
const { RULE_CATEGORY } = require("../../constants");
const { splitToLineNodes } = require("../utils/node");
const { splitToLineNodes, isLine, isTag } = require("../utils/node");
const {
shouldCheckTaggedTemplateExpression,
shouldCheckTemplateLiteral,
Expand Down Expand Up @@ -74,6 +80,17 @@ module.exports = {
minimum: 1,
default: 1,
},
tagChildrenIndent: {
default: {},
type: "object",
patternProperties: {
"^[a-z]+$": {
type: "integer",
minimum: 0,
},
},
additionalProperties: false,
},
},
},
],
Expand All @@ -86,39 +103,44 @@ module.exports = {
const sourceCode = getSourceCode(context);
const indentLevelOptions = (context.options && context.options[1]) || {};
const lines = sourceCode.getLines();
const { indentType, indentSize, indentChar } = (function () {
const options = context.options;
/**
* @type {IndentType['SPACE'] | IndentType['TAB']}
*/
let indentType = INDENT_TYPES.SPACE;
let indentSize = 4;
if (options.length) {
if (options[0] === INDENT_TYPES.TAB) {
indentType = INDENT_TYPES.TAB;
} else {
indentSize = options[0];
}
}
const indentChar =
indentType === INDENT_TYPES.SPACE ? " ".repeat(indentSize) : "\t";
return { indentType, indentSize, indentChar };
})();
const { indentType, indentSize, indentChar } = getIndentOptionInfo(context);

/**
* @param {string} str
* @returns {number}
* @param {Tag} node
* @return {number}
*/
function countLeftPadding(str) {
return str.length - str.replace(/^[\s\t]+/, "").length;
function getTagIncreasingLevel(node) {
if (
node.parent &&
isTag(node.parent) &&
indentLevelOptions &&
typeof indentLevelOptions.tagChildrenIndent === "object" &&
indentLevelOptions.tagChildrenIndent
) {
const option =
indentLevelOptions.tagChildrenIndent[node.parent.name.toLowerCase()];
if (typeof option === "number") {
return option;
}
}

return 1;
}

/**
* @param {AnyNode} node
* @returns {node is Line}
* @return {number}
*/
function isLineNode(node) {
return node.type === "Line";
function getIncreasingLevel(node) {
if (isLine(node)) {
return 1;
}
if (isTag(node)) {
return getTagIncreasingLevel(node);
}
return typeof indentLevelOptions[node.type] === "number"
? indentLevelOptions[node.type]
: 1;
}

/**
Expand All @@ -144,26 +166,22 @@ module.exports = {
*/
function createIndentVisitor(baseLevel) {
const indentLevel = new IndentLevel({
getIncreasingLevel(node) {
return typeof indentLevelOptions[node.type] === "number"
? indentLevelOptions[node.type]
: 1;
},
getIncreasingLevel,
});
indentLevel.setBase(baseLevel);

let parentIgnoringChildCount = 0;

/**
* @param {AnyNode} node
* @param {AnyNode | Line} node
* @returns {string}
*/
function getActualIndent(node) {
const lines = sourceCode.getLines();
const line = lines[node.loc.start.line - 1];
let column = node.loc.start.column;

if (isLineNode(node)) {
if (isLine(node)) {
column += countLeftPadding(node.value);
}

Expand All @@ -177,33 +195,6 @@ module.exports = {
return indentChar.repeat(indentLevel.value());
}

/**
* @param {AnyNode} node
* @param {string} actualIndent
* @return {{ range: Range, loc: SourceLocation }}
*/
function getIndentNodeToReport(node, actualIndent) {
let rangeStart = node.range[0];

if (node.type !== "Line") {
rangeStart -= actualIndent.length;
}

return {
range: [rangeStart, rangeStart + actualIndent.length],
loc: {
start: {
column: 0,
line: node.loc.start.line,
},
end: {
column: actualIndent.length,
line: node.loc.start.line,
},
},
};
}

/**
* @param {string} actualIndent
* @param {number} expectedIndentSize
Expand Down Expand Up @@ -237,7 +228,7 @@ module.exports = {
}

/**
* @param {AnyNode} node
* @param {AnyNode | Line} node
*/
function checkIndent(node) {
if (parentIgnoringChildCount > 0) {
Expand Down Expand Up @@ -289,7 +280,9 @@ module.exports = {
OpenStyleTagStart: checkIndent,
OpenStyleTagEnd: checkIndent,
OpenTagStart: checkIndent,
OpenTagEnd: checkIndent,
OpenTagEnd(node) {
checkIndent(node);
},
CloseTag: checkIndent,
"Tag:exit"(node) {
if (IGNORING_NODES.includes(node.name)) {
Expand All @@ -298,7 +291,6 @@ module.exports = {
indentLevel.dedent(node);
},

// Attribute
Attribute(node) {
indentLevel.indent(node);
},
Expand All @@ -307,8 +299,6 @@ module.exports = {
"Attribute:exit"(node) {
indentLevel.dedent(node);
},

// Text
Text(node) {
indentLevel.indent(node);
const lineNodes = splitToLineNodes(node);
Expand Down Expand Up @@ -369,3 +359,61 @@ module.exports = {
};
},
};

/**
* @param {AnyNode | Line} node
* @param {string} actualIndent
* @return {{range: Range; loc: SourceLocation}}
*/
function getIndentNodeToReport(node, actualIndent) {
let rangeStart = node.range[0];

if (!isLine(node)) {
rangeStart -= actualIndent.length;
}

return {
range: [rangeStart, rangeStart + actualIndent.length],
loc: {
start: {
column: 0,
line: node.loc.start.line,
},
end: {
column: actualIndent.length,
line: node.loc.start.line,
},
},
};
}

/**
* @param {string} str
* @returns {number}
*/
function countLeftPadding(str) {
return str.length - str.replace(/^[\s\t]+/, "").length;
}

/**
* @param {Context} context
* @return {IndentOptionInfo}
*/
function getIndentOptionInfo(context) {
const options = context.options;
/**
* @type {IndentType['SPACE'] | IndentType['TAB']}
*/
let indentType = INDENT_TYPES.SPACE;
let indentSize = 4;
if (options.length) {
if (options[0] === INDENT_TYPES.TAB) {
indentType = INDENT_TYPES.TAB;
} else {
indentSize = options[0];
}
}
const indentChar =
indentType === INDENT_TYPES.SPACE ? " ".repeat(indentSize) : "\t";
return { indentType, indentSize, indentChar };
}
9 changes: 9 additions & 0 deletions packages/eslint-plugin/lib/rules/utils/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,14 @@ function isText(node) {
return node.type === NODE_TYPES.Text;
}

/**
* @param {AnyNode | Line} node
* @returns {node is Line}
*/
function isLine(node) {
return node.type === "Line";
}

const lineBreakPattern = /\r\n|[\r\n\u2028\u2029]/u;
const lineEndingPattern = new RegExp(lineBreakPattern.source, "gu");
/**
Expand Down Expand Up @@ -214,6 +222,7 @@ module.exports = {
isTag,
isComment,
isText,
isLine,
isOverlapWithTemplates,
codeToLines,
isRangesOverlap,
Expand Down
Loading

0 comments on commit 22325ef

Please sign in to comment.