Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/rules/indent.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,7 @@ This rule has an object option:
- `tagChildrenIndent` (default: `{}`): Specifies the indent increment of the child tags of the specified tag. e.g. For example, `"tagChildrenIndent": { "html": 0 }` will set the `<html/>` tag children to 0 indent (2 x 0).

- `ignoreComment` (default: `false`): When set to `true`, the indentation of HTML comments (including opening `<!--`, closing `-->`, and content) will not be checked. This is useful when you want to allow free-form indentation for comments.

- `templateIndentBase` (default: `"templateTag"`): Controls the indentation base for HTML in template literals.
- `"templateTag"`: Uses the indentation of the template tag (e.g., `html\`...\``) as the base indentation.
- `"first"`: Uses the first element of the template literal as the base indentation (elements on the same line as the template tag are ignored).
50 changes: 45 additions & 5 deletions packages/eslint-plugin/lib/rules/indent/indent.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* @property {number} [Option2.Attribute]
* @property {Record<string, number>} [Option2.tagChildrenIndent]
* @property {boolean} [Option2.ignoreComment]
* @property {"first" | "templateTag"} [Option2.templateIndentBase]
*/

const { parseTemplateLiteral } = require("../utils/template-literal");
Expand Down Expand Up @@ -102,6 +103,11 @@ module.exports = {
type: "boolean",
default: false,
},
templateIndentBase: {
type: "string",
enum: ["first", "templateTag"],
default: "templateTag",
},
},
additionalProperties: false,
},
Expand All @@ -116,6 +122,7 @@ module.exports = {
const indentLevelOptions = (context.options && context.options[1]) || {};
const lines = sourceCode.getLines();
const ignoreComment = indentLevelOptions.ignoreComment === true;
const autoBaseIndent = indentLevelOptions.templateIndentBase === "first";
const { indentType, indentSize, indentChar } = getIndentOptionInfo(context);

/**
Expand Down Expand Up @@ -161,12 +168,35 @@ module.exports = {
return 1;
}

/**
* @param {TemplateLiteral} node
* @returns {number}
*/
function getAutoBaseSpaces(node) {
if (!autoBaseIndent) {
return 0;
}
const startLineIndex = node.loc.start.line;
const endLineIndex = node.loc.end.line - 1;
const templateLines = lines.slice(startLineIndex, endLineIndex);
for (let i = 0; i < templateLines.length; i++) {
const line = templateLines[i];
if (line.trim()) {
return countLeftPadding(line);
}
}
return 0;
}

/**
*
* @param {TemplateLiteral} node
* @returns {number}
*/
function getTemplateLiteralBaseIndentLevel(node) {
if (autoBaseIndent) {
return 0;
}
// @ts-ignore
const lineIndex = node.loc.start.line - 1;
const line = lines[lineIndex];
Expand All @@ -181,8 +211,9 @@ module.exports = {

/**
* @param {number} baseLevel
* @param {number} baseSpaces
*/
function createIndentVisitor(baseLevel) {
function createIndentVisitor(baseLevel, baseSpaces) {
const indentLevel = new IndentLevel({
getIncreasingLevel,
});
Expand Down Expand Up @@ -210,7 +241,14 @@ module.exports = {
* @returns {string}
*/
function getExpectedIndent() {
return indentChar.repeat(indentLevel.value());
let base = "";
if (indentType === "space") {
base = " ".repeat(baseSpaces);
} else {
base = indentChar.repeat(baseSpaces);
}

return base + indentChar.repeat(indentLevel.value());
}

/**
Expand Down Expand Up @@ -396,24 +434,26 @@ module.exports = {
}

return {
...createIndentVisitor(0),
...createIndentVisitor(0, 0),
TaggedTemplateExpression(node) {
if (shouldCheckTaggedTemplateExpression(node, context)) {
const base = getTemplateLiteralBaseIndentLevel(node.quasi);
const baseSpaces = getAutoBaseSpaces(node.quasi);
parseTemplateLiteral(
node.quasi,
getSourceCode(context),
createIndentVisitor(base)
createIndentVisitor(base, baseSpaces)
);
}
},
TemplateLiteral(node) {
if (shouldCheckTemplateLiteral(node, context)) {
const base = getTemplateLiteralBaseIndentLevel(node);
const baseSpaces = getAutoBaseSpaces(node);
parseTemplateLiteral(
node,
getSourceCode(context),
createIndentVisitor(base)
createIndentVisitor(base, baseSpaces)
);
}
},
Expand Down
94 changes: 94 additions & 0 deletions packages/eslint-plugin/tests/rules/indent.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1470,6 +1470,23 @@ comment
],
errors: wrongIndentErrors(2),
},
{
code: `
<div>
</div>
`,
output: `
<div>
</div>
`,
options: [
2,
{
templateIndentBase: "first",
},
],
errors: wrongIndentErrors(1),
},
],
};
}
Expand Down Expand Up @@ -1566,6 +1583,51 @@ const code = html\`
},
],
},
{
code: `html\`
<span>test</span>
\``,
options: [
2,
{
templateIndentBase: "first",
},
],
},
{
code: `html\`
<div>
<span>test</span>
</div>
\``,
options: [
2,
{
templateIndentBase: "first",
},
],
},
{
code: `html\`<div>
</div>
<div>a</div>
\``,
options: [
2,
{
templateIndentBase: "first",
},
],
},
{
code: `html\`\`;`,
options: [
2,
{
templateIndentBase: "first",
},
],
},
],
invalid: [
{
Expand Down Expand Up @@ -1896,5 +1958,37 @@ class Component extends LitElement {
options: ["tab", { Attribute: 2, tagChildrenIndent: { span: 2 } }],
errors: wrongIndentErrors(1),
},
{
code: `
const code = html\`
<div>
id="\${bar}">
</div>\`;
`,
output: `
const code = html\`
<div>
id="\${bar}">
</div>\`;
`,
options: [4, { templateIndentBase: "first" }],
errors: wrongIndentErrors(1),
},
{
code: `
const code = html\`
<div
id="\${bar}">
</div>\`;
`,
output: `
const code = html\`
<div
id="\${bar}">
</div>\`;
`,
options: [4, { templateIndentBase: "first" }],
errors: wrongIndentErrors(1),
},
],
});
Loading