Skip to content

Commit

Permalink
feat: add options to set indent level based on node type (#246)
Browse files Browse the repository at this point in the history
* feat: add options to set indent level based on node type

* impl

* Update indent.md

* Update indent.js
  • Loading branch information
yeonjuan authored Dec 8, 2024
1 parent fc08374 commit b2d78f3
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 56 deletions.
44 changes: 35 additions & 9 deletions docs/rules/indent.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,25 @@ module.exports = {

### Options

```ts
//...
"@html-eslint/indent": ["error", "tab" | number]
```
This rule has a mixed option:

This rule has two options.
For 4-space indentation (default option):

- `number(0, 1, ..)` (default 4): requires the use of indentation with specified number of spaces.
```json
{
"@html-eslint/indent": ["error", 4]
}
```

- `"tab"`: requires the use of indentation with tab (`\t`).
Or For tabbed indentation:

Examples of **incorrect** code for this rule:
```json
{
"@html-eslint/indent": ["error", "tab"]
}
```

Examples of **incorrect** code for this rule with the default option:

```html,incorrect
<html>
Expand All @@ -37,9 +44,10 @@ Examples of **incorrect** code for this rule:

Examples of **correct** code for this rule:

<!-- prettier-ignore -->
```html,correct
<html>
<body></body>
<body></body>
</html>
```

Expand Down Expand Up @@ -96,3 +104,21 @@ Examples of **correct** code for this rule:
<body></body>
</html>
```

#### Customizing options

This rule has an object option:

```json
{
"@html-eslint/indent": [
"error",
2,
{
"Attribute": 2
}
]
}
```

- `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).
61 changes: 61 additions & 0 deletions packages/eslint-plugin/lib/rules/indent/indent-level.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* @typedef {import("../../types").AnyNode} AnyNode
* @typedef {{ [key in AnyNode['type']]?: number}} IncLevelOptions
* @typedef {(node: AnyNode) => number} GetIncreasingLevel
*/

class IndentLevel {
/**
* @param {Object} config
* @param {GetIncreasingLevel} config.getIncreasingLevel
*/
constructor(config) {
/**
* @member
* @private
* @type {number}
*/
this.level = -1;
/**
* @member
* @private
* @type {number}
*/
this.baseLevel = 0;
/**
* @member
* @private
*/
this.getInc = config.getIncreasingLevel;
}

/**
* @returns {number}
*/
value() {
return this.level + this.baseLevel;
}

/**
* @param {AnyNode} node
*/
indent(node) {
this.level += this.getInc(node);
}

/**
* @param {AnyNode} node
*/
dedent(node) {
this.level -= this.getInc(node);
}

/**
* @param {number} base
*/
setBase(base) {
this.baseLevel = base;
}
}

module.exports = IndentLevel;
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/**
* @typedef { import("../types").RuleModule } RuleModule
* @typedef { import("../types").AnyNode } AnyNode
* @typedef { import("../types").LineNode } LineNode
* @typedef { import("../types").BaseNode } BaseNode
* @typedef { import("../types").TagNode } TagNode
* @typedef { import("../types").RuleListener } RuleListener
* @typedef { import("../../types").RuleModule } RuleModule
* @typedef { import("../../types").AnyNode } AnyNode
* @typedef { import("../../types").LineNode } LineNode
* @typedef { import("../../types").BaseNode } BaseNode
* @typedef { import("../../types").TagNode } TagNode
* @typedef { import("../../types").RuleListener } RuleListener
* @typedef { import("eslint").AST.Token } Token
* @typedef { import("eslint").SourceCode } SourceCode
* @typedef { import("estree").TemplateLiteral } TemplateLiteral
Expand All @@ -16,13 +16,14 @@
*/

const { parse } = require("@html-eslint/template-parser");
const { RULE_CATEGORY } = require("../constants");
const { splitToLineNodes } = require("./utils/node");
const { RULE_CATEGORY } = require("../../constants");
const { splitToLineNodes } = require("../utils/node");
const {
shouldCheckTaggedTemplateExpression,
shouldCheckTemplateLiteral,
} = require("./utils/settings");
const { getSourceCode } = require("./utils/source-code");
} = require("../utils/settings");
const { getSourceCode } = require("../utils/source-code");
const IndentLevel = require("./indent-level");

/** @type {MessageId} */
const MESSAGE_ID = {
Expand Down Expand Up @@ -63,6 +64,16 @@ module.exports = {
},
],
},
{
type: "object",
properties: {
Attribute: {
type: "integer",
minimum: 1,
default: 1,
},
},
},
],
messages: {
[MESSAGE_ID.WRONG_INDENT]:
Expand All @@ -71,9 +82,15 @@ module.exports = {
},
create(context) {
const sourceCode = getSourceCode(context);
const indentLevelOptions = (context.options && context.options[1]) || {};
const indentLevel = new IndentLevel({
getIncreasingLevel(node) {
return typeof indentLevelOptions[node.type] === "number"
? indentLevelOptions[node.type]
: 1;
},
});

let baseIndentLevel = 0;
let indentLevel = -1;
let parentIgnoringChildCount = 0;

const { indentType, indentSize, indentChar } = (function () {
Expand All @@ -95,16 +112,6 @@ module.exports = {
return { indentType, indentSize, indentChar };
})();

function indent() {
indentLevel++;
}
function unindent() {
indentLevel--;
}
function getIndentLevel() {
return indentLevel + baseIndentLevel;
}

/**
* @param {string} str
* @returns {number}
Expand Down Expand Up @@ -141,7 +148,7 @@ module.exports = {
* @returns {string}
*/
function getExpectedIndent() {
return indentChar.repeat(getIndentLevel());
return indentChar.repeat(indentLevel.value());
}

/**
Expand Down Expand Up @@ -221,7 +228,7 @@ module.exports = {
context.report({
node: targetNode,
messageId: MESSAGE_ID.WRONG_INDENT,
data: getMessageData(actualIndent, getIndentLevel()),
data: getMessageData(actualIndent, indentLevel.value()),
fix(fixer) {
return fixer.replaceText(targetNode, expectedIndent);
},
Expand Down Expand Up @@ -285,38 +292,47 @@ module.exports = {
if (IGNORING_NODES.includes(node.name)) {
parentIgnoringChildCount++;
}
indent();
indentLevel.indent(node);
},
ScriptTag(node) {
indentLevel.indent(node);
},
"ScriptTag:exit"(node) {
indentLevel.dedent(node);
},
ScriptTag: indent,
"ScriptTag:exit": unindent,
OpenScriptTagStart: checkIndent,
OpenScriptTagEnd: checkIndent,
StyleTag: indent,
"StyleTag:exit": unindent,
StyleTag(node) {
indentLevel.indent(node);
},
"StyleTag:exit"(node) {
indentLevel.dedent(node);
},
OpenStyleTagStart: checkIndent,
OpenStyleTagEnd: checkIndent,
OpenTagStart: checkIndent,
OpenTagEnd: checkIndent,
CloseTag: checkIndent,
/**
* @param {TagNode} node
*/
"Tag:exit"(node) {
if (IGNORING_NODES.includes(node.name)) {
parentIgnoringChildCount--;
}
unindent();
indentLevel.dedent(node);
},

// Attribute
Attribute: indent,
Attribute(node) {
indentLevel.indent(node);
},
AttributeKey: checkIndent,
AttributeValue: checkIndent,
"Attribute:exit": unindent,
"Attribute:exit"(node) {
indentLevel.dedent(node);
},

// Text
Text(node) {
indent();
indentLevel.indent(node);
const lineNodes = splitToLineNodes(node);
lineNodes.forEach((lineNode) => {
if (lineNode.skipIndentCheck) {
Expand All @@ -327,13 +343,15 @@ module.exports = {
}
});
},
"Text:exit": unindent,

// Comment
Comment: indent,
"Text:exit"(node) {
indentLevel.dedent(node);
},
Comment(node) {
indentLevel.indent(node);
},
CommentOpen: checkIndent,
CommentContent(node) {
indent();
indentLevel.indent(node);
const lineNodes = splitToLineNodes(node);
lineNodes.forEach((lineNode) => {
if (lineNode.skipIndentCheck) {
Expand All @@ -345,32 +363,36 @@ module.exports = {
});
},
CommentClose: checkIndent,
"Comment:exit": unindent,
"CommentContent:exit": unindent,
"Comment:exit"(node) {
indentLevel.dedent(node);
},
"CommentContent:exit"(node) {
indentLevel.dedent(node);
},
};

return {
...visitor,
TaggedTemplateExpression(node) {
if (shouldCheckTaggedTemplateExpression(node, context)) {
baseIndentLevel = getBaseIndentLevel(node.quasi) + 1;
indentLevel.setBase(getBaseIndentLevel(node.quasi) + 1);
parse(node.quasi, getSourceCode(context), visitor);
}
},
"TaggedTemplateExpression:exit"(node) {
if (shouldCheckTaggedTemplateExpression(node, context)) {
baseIndentLevel = 0;
indentLevel.setBase(0);
}
},
TemplateLiteral(node) {
if (shouldCheckTemplateLiteral(node, context)) {
baseIndentLevel = getBaseIndentLevel(node) + 1;
indentLevel.setBase(getBaseIndentLevel(node) + 1);
parse(node, getSourceCode(context), visitor);
}
},
"TemplateLiteral:exit"(node) {
if (shouldCheckTemplateLiteral(node, context)) {
baseIndentLevel = 0;
indentLevel.setBase(0);
}
},
};
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/lib/rules/indent/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const indent = require("./indent");
module.exports = indent;
Loading

0 comments on commit b2d78f3

Please sign in to comment.