-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat:add attrs-newline rule and close #191
- Loading branch information
1 parent
4c9e5ed
commit 6fc3f80
Showing
7 changed files
with
582 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,8 @@ | |
"roletype", | ||
"nextid", | ||
"screenreader", | ||
"mspace" | ||
"mspace", | ||
"multiline", | ||
"sameline" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
# attrs-newline | ||
|
||
This rule enforces a newline between attributes, when more than a certain number of attributes is present. | ||
|
||
## How to use | ||
|
||
```js,.eslintrc.js | ||
module.exports = { | ||
rules: { | ||
"@html-eslint/attrs-newline": "error", | ||
}, | ||
}; | ||
``` | ||
|
||
## Rule Details | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
<!-- prettier-ignore --> | ||
```html,incorrect | ||
<p class="foo" data-custom id="p"> | ||
<img class="foo" data-custom /> | ||
</p> | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```html,correct | ||
<p | ||
class="foo" | ||
data-custom | ||
id="p" | ||
> | ||
<img class="foo" data-custom /> | ||
</p> | ||
``` | ||
|
||
### Options | ||
|
||
This rule has an object option: | ||
|
||
```ts | ||
//... | ||
"@html-eslint/attrs-newline": ["error", { | ||
"closeStyle": "sameline" | "newline", // Default `"newline"` | ||
"ifAttrsMoreThan": number, // Default `2` | ||
}] | ||
``` | ||
|
||
#### ifAttrsMoreThan | ||
|
||
If there are more than this number of attributes, all attributes should be separated by newlines. Either they should _not_ be separated by newlines. | ||
|
||
The default is `2`. | ||
|
||
Examples of **correct** code for `"ifAttrsMoreThan": 2` | ||
|
||
<!-- prettier-ignore --> | ||
```html | ||
<p class="foo" id="p"> | ||
<img | ||
class="foo" | ||
data-custom | ||
id="img" | ||
/> | ||
</p> | ||
``` | ||
|
||
#### closeStyle | ||
|
||
How the open tag's closing bracket `>` should be spaced: | ||
|
||
- `"newline"`: The closing bracket should be on a newline following the last attribute: | ||
<!-- prettier-ignore --> | ||
```html | ||
<img | ||
class="foo" | ||
data-custom | ||
id="img" | ||
/> | ||
``` | ||
|
||
- `"sameline"`: The closing bracket should be on the same line following the last attribute | ||
<!-- prettier-ignore --> | ||
```html | ||
<img | ||
class="foo" | ||
data-custom | ||
id="img" /> | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
/** | ||
* @typedef { import("../types").RuleFixer } RuleFixer | ||
* @typedef { import("../types").RuleModule } RuleModule | ||
* @typedef { import("../types").TagNode } TagNode | ||
* @typedef {Object} MessageId | ||
* @property {"closeStyleWrong"} CLOSE_STYLE_WRONG | ||
* @property {"newlineMissing"} NEWLINE_MISSING | ||
* @property {"newlineUnexpected"} NEWLINE_UNEXPECTED | ||
*/ | ||
|
||
const { RULE_CATEGORY } = require("../constants"); | ||
|
||
/** | ||
* @type {MessageId} | ||
*/ | ||
|
||
const MESSAGE_ID = { | ||
CLOSE_STYLE_WRONG: "closeStyleWrong", | ||
NEWLINE_MISSING: "newlineMissing", | ||
NEWLINE_UNEXPECTED: "newlineUnexpected", | ||
}; | ||
|
||
/** | ||
* @type {RuleModule} | ||
*/ | ||
module.exports = { | ||
meta: { | ||
type: "code", | ||
|
||
docs: { | ||
description: "Enforce newline between attributes", | ||
category: RULE_CATEGORY.STYLE, | ||
recommended: true, | ||
}, | ||
|
||
fixable: true, | ||
schema: [ | ||
{ | ||
type: "object", | ||
properties: { | ||
closeStyle: { | ||
enum: ["newline", "sameline"], | ||
}, | ||
ifAttrsMoreThan: { | ||
type: "integer", | ||
}, | ||
}, | ||
}, | ||
], | ||
messages: { | ||
[MESSAGE_ID.CLOSE_STYLE_WRONG]: | ||
"Closing bracket was on {{actual}}; expected {{expected}}", | ||
[MESSAGE_ID.NEWLINE_MISSING]: "Newline expected before {{attrName}}", | ||
[MESSAGE_ID.NEWLINE_UNEXPECTED]: | ||
"Newlines not expected between attributes, since this tag has fewer than {{attrMin}} attributes", | ||
}, | ||
}, | ||
|
||
create(context) { | ||
const options = context.options[0] || {}; | ||
const attrMin = isNaN(options.ifAttrsMoreThan) | ||
? 2 | ||
: options.ifAttrsMoreThan; | ||
const closeStyle = options.closeStyle || "newline"; | ||
|
||
return { | ||
/** | ||
* @param {TagNode} node | ||
*/ | ||
Tag(node) { | ||
const shouldBeMultiline = node.attributes.length > attrMin; | ||
|
||
/** | ||
* This doesn't do any indentation, so the result will look silly. Indentation should be covered by the `indent` rule | ||
* @param {RuleFixer} fixer | ||
*/ | ||
function fix(fixer) { | ||
const spacer = shouldBeMultiline ? "\n" : " "; | ||
let expected = node.openStart.value; | ||
for (const attr of node.attributes) { | ||
expected += `${spacer}${attr.key.value}`; | ||
if (attr.startWrapper && attr.value && attr.endWrapper) { | ||
expected += `=${attr.startWrapper.value}${attr.value.value}${attr.endWrapper.value}`; | ||
} | ||
} | ||
if (shouldBeMultiline && closeStyle === "newline") { | ||
expected += "\n"; | ||
} else if (node.selfClosing) { | ||
expected += " "; | ||
} | ||
expected += node.openEnd.value; | ||
|
||
return fixer.replaceTextRange( | ||
[node.openStart.range[0], node.openEnd.range[1]], | ||
expected | ||
); | ||
} | ||
|
||
if (shouldBeMultiline) { | ||
let index = 0; | ||
for (const attr of node.attributes) { | ||
const attrPrevious = node.attributes[index - 1]; | ||
const relativeToNode = attrPrevious || node.openStart; | ||
if (attr.loc.start.line === relativeToNode.loc.end.line) { | ||
return context.report({ | ||
node, | ||
data: { | ||
attrName: attr.key.value, | ||
}, | ||
fix, | ||
messageId: MESSAGE_ID.NEWLINE_MISSING, | ||
}); | ||
} | ||
index += 1; | ||
} | ||
|
||
const attrLast = node.attributes[node.attributes.length - 1]; | ||
const closeStyleActual = | ||
node.openEnd.loc.start.line === attrLast.loc.end.line | ||
? "sameline" | ||
: "newline"; | ||
if (closeStyle !== closeStyleActual) { | ||
return context.report({ | ||
node, | ||
data: { | ||
actual: closeStyleActual, | ||
expected: closeStyle, | ||
}, | ||
fix, | ||
messageId: MESSAGE_ID.CLOSE_STYLE_WRONG, | ||
}); | ||
} | ||
} else { | ||
let expectedLastLineNum = node.openStart.loc.start.line; | ||
for (const attr of node.attributes) { | ||
if (shouldBeMultiline) { | ||
expectedLastLineNum += 1; | ||
} | ||
if (attr.value) { | ||
const valueLineSpan = | ||
attr.value.loc.end.line - attr.value.loc.start.line; | ||
expectedLastLineNum += valueLineSpan; | ||
} | ||
} | ||
if (shouldBeMultiline && closeStyle === "newline") { | ||
expectedLastLineNum += 1; | ||
} | ||
|
||
if (node.openEnd.loc.end.line !== expectedLastLineNum) { | ||
return context.report({ | ||
node, | ||
data: { | ||
attrMin, | ||
}, | ||
fix, | ||
messageId: MESSAGE_ID.NEWLINE_UNEXPECTED, | ||
}); | ||
} | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.