@html-eslint/eslint-plugin 0.24.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/configs/recommended.js +1 -0
- package/lib/rules/attrs-newline.js +163 -0
- package/lib/rules/index.js +2 -0
- package/lib/rules/no-extra-spacing-attrs.js +121 -87
- package/lib/rules/require-closing-tags.js +45 -16
- package/package.json +3 -3
- package/types/configs/recommended.d.ts +18 -0
- package/types/configs/recommended.d.ts.map +1 -0
- package/types/constants/index.d.ts +5 -0
- package/types/constants/index.d.ts.map +1 -0
- package/types/constants/obsolete-tags.d.ts +3 -0
- package/types/constants/obsolete-tags.d.ts.map +1 -0
- package/types/constants/rule-category.d.ts +5 -0
- package/types/constants/rule-category.d.ts.map +1 -0
- package/types/constants/void-elements.d.ts +3 -0
- package/types/constants/void-elements.d.ts.map +1 -0
- package/types/rules/attrs-newline.d.ts +11 -0
- package/types/rules/attrs-newline.d.ts.map +1 -0
- package/types/rules/element-newline.d.ts +7 -0
- package/types/rules/element-newline.d.ts.map +1 -0
- package/types/rules/id-naming-convention.d.ts +7 -0
- package/types/rules/indent.d.ts +15 -0
- package/types/rules/indent.d.ts.map +1 -0
- package/types/rules/index.d.ts +42 -0
- package/types/rules/lowercase.d.ts +7 -0
- package/types/rules/no-abstract-roles.d.ts +7 -0
- package/types/rules/no-abstract-roles.d.ts.map +1 -0
- package/types/rules/no-accesskey-attrs.d.ts +7 -0
- package/types/rules/no-accesskey-attrs.d.ts.map +1 -0
- package/types/rules/no-aria-hidden-body.d.ts +4 -0
- package/types/rules/no-aria-hidden-body.d.ts.map +1 -0
- package/types/rules/no-duplicate-attrs.d.ts +7 -0
- package/types/rules/no-duplicate-attrs.d.ts.map +1 -0
- package/types/rules/no-duplicate-id.d.ts +7 -0
- package/types/rules/no-duplicate-id.d.ts.map +1 -0
- package/types/rules/no-extra-spacing-attrs.d.ts +15 -0
- package/types/rules/no-extra-spacing-attrs.d.ts.map +1 -0
- package/types/rules/no-inline-styles.d.ts +4 -0
- package/types/rules/no-inline-styles.d.ts.map +1 -0
- package/types/rules/no-multiple-empty-lines.d.ts +5 -0
- package/types/rules/no-multiple-empty-lines.d.ts.map +1 -0
- package/types/rules/no-multiple-h1.d.ts +5 -0
- package/types/rules/no-multiple-h1.d.ts.map +1 -0
- package/types/rules/no-non-scalable-viewport.d.ts +4 -0
- package/types/rules/no-non-scalable-viewport.d.ts.map +1 -0
- package/types/rules/no-obsolete-tags.d.ts +4 -0
- package/types/rules/no-obsolete-tags.d.ts.map +1 -0
- package/types/rules/no-positive-tabindex.d.ts +7 -0
- package/types/rules/no-positive-tabindex.d.ts.map +1 -0
- package/types/rules/no-restricted-attr-values.d.ts +13 -0
- package/types/rules/no-restricted-attr-values.d.ts.map +1 -0
- package/types/rules/no-restricted-attrs.d.ts +13 -0
- package/types/rules/no-script-style-type.d.ts +7 -0
- package/types/rules/no-script-style-type.d.ts.map +1 -0
- package/types/rules/no-skip-heading-levels.d.ts +5 -0
- package/types/rules/no-skip-heading-levels.d.ts.map +1 -0
- package/types/rules/no-target-blank.d.ts +4 -0
- package/types/rules/no-target-blank.d.ts.map +1 -0
- package/types/rules/no-trailing-spaces.d.ts +4 -0
- package/types/rules/no-trailing-spaces.d.ts.map +1 -0
- package/types/rules/quotes.d.ts +9 -0
- package/types/rules/quotes.d.ts.map +1 -0
- package/types/rules/require-attrs.d.ts +7 -0
- package/types/rules/require-button-type.d.ts +4 -0
- package/types/rules/require-button-type.d.ts.map +1 -0
- package/types/rules/require-closing-tags.d.ts +5 -0
- package/types/rules/require-closing-tags.d.ts.map +1 -0
- package/types/rules/require-doctype.d.ts +4 -0
- package/types/rules/require-doctype.d.ts.map +1 -0
- package/types/rules/require-frame-title.d.ts +4 -0
- package/types/rules/require-frame-title.d.ts.map +1 -0
- package/types/rules/require-img-alt.d.ts +5 -0
- package/types/rules/require-img-alt.d.ts.map +1 -0
- package/types/rules/require-lang.d.ts +4 -0
- package/types/rules/require-lang.d.ts.map +1 -0
- package/types/rules/require-li-container.d.ts +4 -0
- package/types/rules/require-meta-charset.d.ts +5 -0
- package/types/rules/require-meta-description.d.ts +5 -0
- package/types/rules/require-meta-viewport.d.ts +5 -0
- package/types/rules/require-open-graph-protocol.d.ts +5 -0
- package/types/rules/require-title.d.ts +6 -0
- package/types/rules/sort-attrs.d.ts +7 -0
- package/types/rules/sort-attrs.d.ts.map +1 -0
- package/types/rules/utils/array.d.ts +17 -0
- package/types/rules/utils/array.d.ts.map +1 -0
- package/types/rules/utils/naming.d.ts +25 -0
- package/types/rules/utils/naming.d.ts.map +1 -0
- package/types/rules/utils/node.d.ts +37 -0
- package/types/rules/utils/node.d.ts.map +1 -0
- package/types/constants/svg-camelcase-attributes.d.ts +0 -3
- package/types/constants/svg-camelcase-attributes.d.ts.map +0 -1
|
@@ -6,6 +6,7 @@ module.exports = {
|
|
|
6
6
|
"@html-eslint/require-title": "error",
|
|
7
7
|
"@html-eslint/no-multiple-h1": "error",
|
|
8
8
|
"@html-eslint/no-extra-spacing-attrs": "error",
|
|
9
|
+
"@html-eslint/attrs-newline": "error",
|
|
9
10
|
"@html-eslint/element-newline": "error",
|
|
10
11
|
"@html-eslint/no-duplicate-id": "error",
|
|
11
12
|
"@html-eslint/indent": "error",
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef { import("../types").RuleFixer } RuleFixer
|
|
3
|
+
* @typedef { import("../types").RuleModule } RuleModule
|
|
4
|
+
* @typedef { import("../types").TagNode } TagNode
|
|
5
|
+
* @typedef {Object} MessageId
|
|
6
|
+
* @property {"closeStyleWrong"} CLOSE_STYLE_WRONG
|
|
7
|
+
* @property {"newlineMissing"} NEWLINE_MISSING
|
|
8
|
+
* @property {"newlineUnexpected"} NEWLINE_UNEXPECTED
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { RULE_CATEGORY } = require("../constants");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @type {MessageId}
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const MESSAGE_ID = {
|
|
18
|
+
CLOSE_STYLE_WRONG: "closeStyleWrong",
|
|
19
|
+
NEWLINE_MISSING: "newlineMissing",
|
|
20
|
+
NEWLINE_UNEXPECTED: "newlineUnexpected",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @type {RuleModule}
|
|
25
|
+
*/
|
|
26
|
+
module.exports = {
|
|
27
|
+
meta: {
|
|
28
|
+
type: "code",
|
|
29
|
+
|
|
30
|
+
docs: {
|
|
31
|
+
description: "Enforce newline between attributes",
|
|
32
|
+
category: RULE_CATEGORY.STYLE,
|
|
33
|
+
recommended: true,
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
fixable: true,
|
|
37
|
+
schema: [
|
|
38
|
+
{
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
closeStyle: {
|
|
42
|
+
enum: ["newline", "sameline"],
|
|
43
|
+
},
|
|
44
|
+
ifAttrsMoreThan: {
|
|
45
|
+
type: "integer",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
messages: {
|
|
51
|
+
[MESSAGE_ID.CLOSE_STYLE_WRONG]:
|
|
52
|
+
"Closing bracket was on {{actual}}; expected {{expected}}",
|
|
53
|
+
[MESSAGE_ID.NEWLINE_MISSING]: "Newline expected before {{attrName}}",
|
|
54
|
+
[MESSAGE_ID.NEWLINE_UNEXPECTED]:
|
|
55
|
+
"Newlines not expected between attributes, since this tag has fewer than {{attrMin}} attributes",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
create(context) {
|
|
60
|
+
const options = context.options[0] || {};
|
|
61
|
+
const attrMin = isNaN(options.ifAttrsMoreThan)
|
|
62
|
+
? 2
|
|
63
|
+
: options.ifAttrsMoreThan;
|
|
64
|
+
const closeStyle = options.closeStyle || "newline";
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
/**
|
|
68
|
+
* @param {TagNode} node
|
|
69
|
+
*/
|
|
70
|
+
Tag(node) {
|
|
71
|
+
const shouldBeMultiline = node.attributes.length > attrMin;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* This doesn't do any indentation, so the result will look silly. Indentation should be covered by the `indent` rule
|
|
75
|
+
* @param {RuleFixer} fixer
|
|
76
|
+
*/
|
|
77
|
+
function fix(fixer) {
|
|
78
|
+
const spacer = shouldBeMultiline ? "\n" : " ";
|
|
79
|
+
let expected = node.openStart.value;
|
|
80
|
+
for (const attr of node.attributes) {
|
|
81
|
+
expected += `${spacer}${attr.key.value}`;
|
|
82
|
+
if (attr.startWrapper && attr.value && attr.endWrapper) {
|
|
83
|
+
expected += `=${attr.startWrapper.value}${attr.value.value}${attr.endWrapper.value}`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
if (shouldBeMultiline && closeStyle === "newline") {
|
|
87
|
+
expected += "\n";
|
|
88
|
+
} else if (node.selfClosing) {
|
|
89
|
+
expected += " ";
|
|
90
|
+
}
|
|
91
|
+
expected += node.openEnd.value;
|
|
92
|
+
|
|
93
|
+
return fixer.replaceTextRange(
|
|
94
|
+
[node.openStart.range[0], node.openEnd.range[1]],
|
|
95
|
+
expected
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (shouldBeMultiline) {
|
|
100
|
+
let index = 0;
|
|
101
|
+
for (const attr of node.attributes) {
|
|
102
|
+
const attrPrevious = node.attributes[index - 1];
|
|
103
|
+
const relativeToNode = attrPrevious || node.openStart;
|
|
104
|
+
if (attr.loc.start.line === relativeToNode.loc.end.line) {
|
|
105
|
+
return context.report({
|
|
106
|
+
node,
|
|
107
|
+
data: {
|
|
108
|
+
attrName: attr.key.value,
|
|
109
|
+
},
|
|
110
|
+
fix,
|
|
111
|
+
messageId: MESSAGE_ID.NEWLINE_MISSING,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
index += 1;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const attrLast = node.attributes[node.attributes.length - 1];
|
|
118
|
+
const closeStyleActual =
|
|
119
|
+
node.openEnd.loc.start.line === attrLast.loc.end.line
|
|
120
|
+
? "sameline"
|
|
121
|
+
: "newline";
|
|
122
|
+
if (closeStyle !== closeStyleActual) {
|
|
123
|
+
return context.report({
|
|
124
|
+
node,
|
|
125
|
+
data: {
|
|
126
|
+
actual: closeStyleActual,
|
|
127
|
+
expected: closeStyle,
|
|
128
|
+
},
|
|
129
|
+
fix,
|
|
130
|
+
messageId: MESSAGE_ID.CLOSE_STYLE_WRONG,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
let expectedLastLineNum = node.openStart.loc.start.line;
|
|
135
|
+
for (const attr of node.attributes) {
|
|
136
|
+
if (shouldBeMultiline) {
|
|
137
|
+
expectedLastLineNum += 1;
|
|
138
|
+
}
|
|
139
|
+
if (attr.value) {
|
|
140
|
+
const valueLineSpan =
|
|
141
|
+
attr.value.loc.end.line - attr.value.loc.start.line;
|
|
142
|
+
expectedLastLineNum += valueLineSpan;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (shouldBeMultiline && closeStyle === "newline") {
|
|
146
|
+
expectedLastLineNum += 1;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (node.openEnd.loc.end.line !== expectedLastLineNum) {
|
|
150
|
+
return context.report({
|
|
151
|
+
node,
|
|
152
|
+
data: {
|
|
153
|
+
attrMin,
|
|
154
|
+
},
|
|
155
|
+
fix,
|
|
156
|
+
messageId: MESSAGE_ID.NEWLINE_UNEXPECTED,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
};
|
package/lib/rules/index.js
CHANGED
|
@@ -6,6 +6,7 @@ const noDuplicateId = require("./no-duplicate-id");
|
|
|
6
6
|
const noInlineStyles = require("./no-inline-styles");
|
|
7
7
|
const noMultipleH1 = require("./no-multiple-h1");
|
|
8
8
|
const noExtraSpacingAttrs = require("./no-extra-spacing-attrs");
|
|
9
|
+
const attrsNewline = require("./attrs-newline");
|
|
9
10
|
const elementNewLine = require("./element-newline");
|
|
10
11
|
const noSkipHeadingLevels = require("./no-skip-heading-levels");
|
|
11
12
|
const indent = require("./indent");
|
|
@@ -45,6 +46,7 @@ module.exports = {
|
|
|
45
46
|
"no-inline-styles": noInlineStyles,
|
|
46
47
|
"no-multiple-h1": noMultipleH1,
|
|
47
48
|
"no-extra-spacing-attrs": noExtraSpacingAttrs,
|
|
49
|
+
"attrs-newline": attrsNewline,
|
|
48
50
|
"element-newline": elementNewLine,
|
|
49
51
|
"no-skip-heading-levels": noSkipHeadingLevels,
|
|
50
52
|
"require-li-container": requireLiContainer,
|
|
@@ -20,9 +20,13 @@ const MESSAGE_IDS = {
|
|
|
20
20
|
EXTRA_BETWEEN: "unexpectedBetween",
|
|
21
21
|
EXTRA_AFTER: "unexpectedAfter",
|
|
22
22
|
EXTRA_BEFORE: "unexpectedBefore",
|
|
23
|
+
EXTRA_BEFORE_CLOSE: "unexpectedBeforeClose",
|
|
23
24
|
MISSING_BEFORE: "missingBefore",
|
|
24
25
|
MISSING_BEFORE_SELF_CLOSE: "missingBeforeSelfClose",
|
|
25
26
|
EXTRA_BEFORE_SELF_CLOSE: "unexpectedBeforeSelfClose",
|
|
27
|
+
EXTRA_TAB_BEFORE: "unexpectedTabBefore",
|
|
28
|
+
EXTRA_TAB_BEFORE_SELF_CLOSE: "unexpectedTabBeforeSelfClose",
|
|
29
|
+
EXTRA_TAB_BETWEEN: "unexpectedTabBetween",
|
|
26
30
|
};
|
|
27
31
|
|
|
28
32
|
/**
|
|
@@ -46,6 +50,9 @@ module.exports = {
|
|
|
46
50
|
disallowMissing: {
|
|
47
51
|
type: "boolean",
|
|
48
52
|
},
|
|
53
|
+
disallowTabs: {
|
|
54
|
+
type: "boolean",
|
|
55
|
+
},
|
|
49
56
|
enforceBeforeSelfClose: {
|
|
50
57
|
type: "boolean",
|
|
51
58
|
},
|
|
@@ -56,17 +63,27 @@ module.exports = {
|
|
|
56
63
|
[MESSAGE_IDS.EXTRA_BETWEEN]: "Unexpected space between attributes",
|
|
57
64
|
[MESSAGE_IDS.EXTRA_AFTER]: "Unexpected space after attribute",
|
|
58
65
|
[MESSAGE_IDS.EXTRA_BEFORE]: "Unexpected space before attribute",
|
|
66
|
+
[MESSAGE_IDS.EXTRA_BEFORE_CLOSE]: "Unexpected space before closing",
|
|
59
67
|
[MESSAGE_IDS.MISSING_BEFORE_SELF_CLOSE]:
|
|
60
68
|
"Missing space before self closing",
|
|
61
69
|
[MESSAGE_IDS.EXTRA_BEFORE_SELF_CLOSE]:
|
|
62
70
|
"Unexpected extra spaces before self closing",
|
|
63
71
|
[MESSAGE_IDS.MISSING_BEFORE]: "Missing space before attribute",
|
|
72
|
+
[MESSAGE_IDS.EXTRA_TAB_BEFORE]:
|
|
73
|
+
"Unexpected tab before attribute; use space instead",
|
|
74
|
+
[MESSAGE_IDS.EXTRA_TAB_BEFORE_SELF_CLOSE]:
|
|
75
|
+
"Unexpected tab before self closing; use space instead",
|
|
76
|
+
[MESSAGE_IDS.EXTRA_TAB_BETWEEN]:
|
|
77
|
+
"Unexpected tab between attributes; use space instead",
|
|
64
78
|
},
|
|
65
79
|
},
|
|
66
80
|
create(context) {
|
|
67
81
|
const enforceBeforeSelfClose = !!(context.options[0] || {})
|
|
68
82
|
.enforceBeforeSelfClose;
|
|
69
83
|
const disallowMissing = !!(context.options[0] || {}).disallowMissing;
|
|
84
|
+
const disallowTabs = !!(context.options[0] || {}).disallowTabs;
|
|
85
|
+
|
|
86
|
+
const sourceCode = context.getSourceCode().text;
|
|
70
87
|
|
|
71
88
|
/**
|
|
72
89
|
* @param {AttributeNode[]} attrs
|
|
@@ -98,48 +115,23 @@ module.exports = {
|
|
|
98
115
|
return fixer.insertTextAfter(current, " ");
|
|
99
116
|
},
|
|
100
117
|
});
|
|
118
|
+
} else if (disallowTabs) {
|
|
119
|
+
if (sourceCode[current.range[1]] === `\t`) {
|
|
120
|
+
context.report({
|
|
121
|
+
loc: getLocBetween(current, after),
|
|
122
|
+
messageId: MESSAGE_IDS.EXTRA_TAB_BETWEEN,
|
|
123
|
+
fix(fixer) {
|
|
124
|
+
return fixer.replaceTextRange(
|
|
125
|
+
[current.range[1], current.range[1] + 1],
|
|
126
|
+
` `
|
|
127
|
+
);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
101
131
|
}
|
|
102
132
|
});
|
|
103
133
|
}
|
|
104
134
|
|
|
105
|
-
/**
|
|
106
|
-
* @param {OpenTagEndNode | OpenScriptTagEndNode | OpenStyleTagEndNode} openEnd
|
|
107
|
-
* @param {AttributeNode} lastAttr
|
|
108
|
-
* @param {boolean} isSelfClosed
|
|
109
|
-
* @returns {void}
|
|
110
|
-
*/
|
|
111
|
-
function checkExtraSpaceAfter(openEnd, lastAttr, isSelfClosed) {
|
|
112
|
-
if (openEnd.loc.end.line !== lastAttr.loc.end.line) {
|
|
113
|
-
// skip the attribute on the different line with the start tag
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
const limit = isSelfClosed && enforceBeforeSelfClose ? 1 : 0;
|
|
117
|
-
const spacesBetween = openEnd.loc.start.column - lastAttr.loc.end.column;
|
|
118
|
-
|
|
119
|
-
if (spacesBetween > limit) {
|
|
120
|
-
context.report({
|
|
121
|
-
loc: getLocBetween(lastAttr, openEnd),
|
|
122
|
-
messageId: MESSAGE_IDS.EXTRA_AFTER,
|
|
123
|
-
fix(fixer) {
|
|
124
|
-
return fixer.removeRange([
|
|
125
|
-
lastAttr.range[1],
|
|
126
|
-
lastAttr.range[1] + spacesBetween - limit,
|
|
127
|
-
]);
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (isSelfClosed && enforceBeforeSelfClose && spacesBetween < 1) {
|
|
133
|
-
context.report({
|
|
134
|
-
loc: getLocBetween(lastAttr, openEnd),
|
|
135
|
-
messageId: MESSAGE_IDS.MISSING_BEFORE_SELF_CLOSE,
|
|
136
|
-
fix(fixer) {
|
|
137
|
-
return fixer.insertTextAfter(lastAttr, " ");
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
135
|
/**
|
|
144
136
|
* @param {OpenScriptTagStartNode | OpenTagStartNode | OpenStyleTagStartNode} node
|
|
145
137
|
* @param {AttributeNode} firstAttr
|
|
@@ -164,42 +156,19 @@ module.exports = {
|
|
|
164
156
|
]);
|
|
165
157
|
},
|
|
166
158
|
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const spacesBetween =
|
|
181
|
-
openEnd.loc.start.column - beforeSelfClosing.loc.end.column;
|
|
182
|
-
const locBetween = getLocBetween(beforeSelfClosing, openEnd);
|
|
183
|
-
|
|
184
|
-
if (spacesBetween > 1) {
|
|
185
|
-
context.report({
|
|
186
|
-
loc: locBetween,
|
|
187
|
-
messageId: MESSAGE_IDS.EXTRA_BEFORE_SELF_CLOSE,
|
|
188
|
-
fix(fixer) {
|
|
189
|
-
return fixer.removeRange([
|
|
190
|
-
beforeSelfClosing.range[1] + 1,
|
|
191
|
-
openEnd.range[0],
|
|
192
|
-
]);
|
|
193
|
-
},
|
|
194
|
-
});
|
|
195
|
-
} else if (spacesBetween < 1) {
|
|
196
|
-
context.report({
|
|
197
|
-
loc: locBetween,
|
|
198
|
-
messageId: MESSAGE_IDS.MISSING_BEFORE_SELF_CLOSE,
|
|
199
|
-
fix(fixer) {
|
|
200
|
-
return fixer.insertTextAfter(beforeSelfClosing, " ");
|
|
201
|
-
},
|
|
202
|
-
});
|
|
159
|
+
} else if (disallowTabs) {
|
|
160
|
+
if (sourceCode[firstAttr.range[0] - 1] === `\t`) {
|
|
161
|
+
context.report({
|
|
162
|
+
loc: firstAttr.loc,
|
|
163
|
+
messageId: MESSAGE_IDS.EXTRA_TAB_BEFORE,
|
|
164
|
+
fix(fixer) {
|
|
165
|
+
return fixer.replaceTextRange(
|
|
166
|
+
[firstAttr.range[0] - 1, firstAttr.range[0]],
|
|
167
|
+
` `
|
|
168
|
+
);
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
}
|
|
203
172
|
}
|
|
204
173
|
}
|
|
205
174
|
|
|
@@ -216,24 +185,89 @@ module.exports = {
|
|
|
216
185
|
if (node.attributes.length) {
|
|
217
186
|
checkExtraSpaceBefore(node.openStart, node.attributes[0]);
|
|
218
187
|
}
|
|
188
|
+
|
|
219
189
|
if (node.openEnd) {
|
|
190
|
+
checkExtraSpacesBetweenAttrs(node.attributes);
|
|
191
|
+
|
|
192
|
+
const lastAttr = node.attributes[node.attributes.length - 1];
|
|
193
|
+
const nodeBeforeEnd =
|
|
194
|
+
node.attributes.length === 0 ? node.openStart : lastAttr;
|
|
195
|
+
|
|
196
|
+
if (nodeBeforeEnd.loc.end.line !== node.openEnd.loc.start.line) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
220
200
|
const isSelfClosing = node.openEnd.value === "/>";
|
|
221
201
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
)
|
|
202
|
+
const spacesBetween =
|
|
203
|
+
node.openEnd.loc.start.column - nodeBeforeEnd.loc.end.column;
|
|
204
|
+
const locBetween = getLocBetween(nodeBeforeEnd, node.openEnd);
|
|
205
|
+
|
|
206
|
+
if (isSelfClosing && enforceBeforeSelfClose) {
|
|
207
|
+
if (spacesBetween < 1) {
|
|
208
|
+
context.report({
|
|
209
|
+
loc: locBetween,
|
|
210
|
+
messageId: MESSAGE_IDS.MISSING_BEFORE_SELF_CLOSE,
|
|
211
|
+
fix(fixer) {
|
|
212
|
+
return fixer.insertTextAfter(nodeBeforeEnd, " ");
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
} else if (spacesBetween === 1) {
|
|
216
|
+
if (
|
|
217
|
+
disallowTabs &&
|
|
218
|
+
sourceCode[node.openEnd.range[0] - 1] === `\t`
|
|
219
|
+
) {
|
|
220
|
+
context.report({
|
|
221
|
+
loc: node.openEnd.loc,
|
|
222
|
+
messageId: MESSAGE_IDS.EXTRA_TAB_BEFORE_SELF_CLOSE,
|
|
223
|
+
fix(fixer) {
|
|
224
|
+
return fixer.replaceTextRange(
|
|
225
|
+
[node.openEnd.range[0] - 1, node.openEnd.range[0]],
|
|
226
|
+
` `
|
|
227
|
+
);
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
context.report({
|
|
233
|
+
loc: locBetween,
|
|
234
|
+
messageId: MESSAGE_IDS.EXTRA_BEFORE_SELF_CLOSE,
|
|
235
|
+
fix(fixer) {
|
|
236
|
+
return fixer.removeRange([
|
|
237
|
+
nodeBeforeEnd.range[1] + 1,
|
|
238
|
+
node.openEnd.range[0],
|
|
239
|
+
]);
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return;
|
|
228
245
|
}
|
|
229
246
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
247
|
+
if (spacesBetween > 0) {
|
|
248
|
+
if (node.attributes.length > 0) {
|
|
249
|
+
context.report({
|
|
250
|
+
loc: locBetween,
|
|
251
|
+
messageId: MESSAGE_IDS.EXTRA_AFTER,
|
|
252
|
+
fix(fixer) {
|
|
253
|
+
return fixer.removeRange([
|
|
254
|
+
lastAttr.range[1],
|
|
255
|
+
node.openEnd.range[0],
|
|
256
|
+
]);
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
} else {
|
|
260
|
+
context.report({
|
|
261
|
+
loc: locBetween,
|
|
262
|
+
messageId: MESSAGE_IDS.EXTRA_BEFORE_CLOSE,
|
|
263
|
+
fix(fixer) {
|
|
264
|
+
return fixer.removeRange([
|
|
265
|
+
node.openStart.range[1],
|
|
266
|
+
node.openEnd.range[0],
|
|
267
|
+
]);
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}
|
|
237
271
|
}
|
|
238
272
|
}
|
|
239
273
|
},
|
|
@@ -34,8 +34,11 @@ module.exports = {
|
|
|
34
34
|
selfClosing: {
|
|
35
35
|
enum: ["always", "never"],
|
|
36
36
|
},
|
|
37
|
-
|
|
38
|
-
type: "
|
|
37
|
+
selfClosingCustomPatterns: {
|
|
38
|
+
type: "array",
|
|
39
|
+
items: {
|
|
40
|
+
type: "string",
|
|
41
|
+
},
|
|
39
42
|
},
|
|
40
43
|
},
|
|
41
44
|
additionalProperties: false,
|
|
@@ -49,14 +52,21 @@ module.exports = {
|
|
|
49
52
|
},
|
|
50
53
|
|
|
51
54
|
create(context) {
|
|
52
|
-
|
|
55
|
+
/** @type {string[]} */
|
|
56
|
+
const foreignContext = [];
|
|
57
|
+
const shouldSelfCloseVoid =
|
|
53
58
|
context.options && context.options.length
|
|
54
59
|
? context.options[0].selfClosing === "always"
|
|
55
60
|
: false;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
/** @type {string[]} */
|
|
62
|
+
const selfClosingCustomPatternsOption =
|
|
63
|
+
(context.options &&
|
|
64
|
+
context.options.length &&
|
|
65
|
+
context.options[0].selfClosingCustomPatterns) ||
|
|
66
|
+
[];
|
|
67
|
+
const selfClosingCustomPatterns = selfClosingCustomPatternsOption.map(
|
|
68
|
+
(i) => new RegExp(i)
|
|
69
|
+
);
|
|
60
70
|
|
|
61
71
|
/**
|
|
62
72
|
* @param {TagNode} node
|
|
@@ -91,7 +101,10 @@ module.exports = {
|
|
|
91
101
|
if (!fixable) {
|
|
92
102
|
return null;
|
|
93
103
|
}
|
|
94
|
-
|
|
104
|
+
const fixes = [];
|
|
105
|
+
fixes.push(fixer.replaceText(node.openEnd, " />"));
|
|
106
|
+
if (node.close) fixes.push(fixer.remove(node.close));
|
|
107
|
+
return fixes;
|
|
95
108
|
},
|
|
96
109
|
});
|
|
97
110
|
}
|
|
@@ -115,17 +128,33 @@ module.exports = {
|
|
|
115
128
|
return {
|
|
116
129
|
Tag(node) {
|
|
117
130
|
const isVoidElement = VOID_ELEMENTS_SET.has(node.name);
|
|
118
|
-
|
|
119
|
-
node.
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
const isSelfClosingCustomElement = !!selfClosingCustomPatterns.some(
|
|
132
|
+
(i) => node.name.match(i)
|
|
133
|
+
);
|
|
134
|
+
const isForeign = foreignContext.length > 0;
|
|
135
|
+
const shouldSelfCloseCustom =
|
|
136
|
+
isSelfClosingCustomElement && !node.children.length;
|
|
137
|
+
const shouldSelfCloseForeign = node.selfClosing;
|
|
138
|
+
const shouldSelfClose =
|
|
139
|
+
(isVoidElement && shouldSelfCloseVoid) ||
|
|
140
|
+
(isSelfClosingCustomElement && shouldSelfCloseCustom) ||
|
|
141
|
+
(isForeign && shouldSelfCloseForeign);
|
|
142
|
+
const canSelfClose =
|
|
143
|
+
isVoidElement || isSelfClosingCustomElement || isForeign;
|
|
144
|
+
if (node.selfClosing || canSelfClose) {
|
|
145
|
+
checkVoidElement(node, shouldSelfClose, canSelfClose);
|
|
126
146
|
} else if (node.openEnd.value !== "/>") {
|
|
127
147
|
checkClosingTag(node);
|
|
128
148
|
}
|
|
149
|
+
if (["svg", "math"].includes(node.name)) foreignContext.push(node.name);
|
|
150
|
+
},
|
|
151
|
+
/**
|
|
152
|
+
* @param {TagNode} node
|
|
153
|
+
*/
|
|
154
|
+
"Tag:exit"(node) {
|
|
155
|
+
if (node.name === foreignContext[foreignContext.length - 1]) {
|
|
156
|
+
foreignContext.pop();
|
|
157
|
+
}
|
|
129
158
|
},
|
|
130
159
|
};
|
|
131
160
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@html-eslint/eslint-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
4
4
|
"description": "ESLint plugin for html",
|
|
5
5
|
"author": "yeonjuan",
|
|
6
6
|
"homepage": "https://github.com/yeonjuan/html-eslint#readme",
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
"accessibility"
|
|
46
46
|
],
|
|
47
47
|
"devDependencies": {
|
|
48
|
-
"@html-eslint/parser": "^0.
|
|
48
|
+
"@html-eslint/parser": "^0.26.0",
|
|
49
49
|
"@types/eslint": "^8.56.2",
|
|
50
50
|
"@types/estree": "^0.0.47",
|
|
51
51
|
"es-html-parser": "^0.0.8",
|
|
52
52
|
"typescript": "^4.4.4"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "34d55c3b5be5a29cc416063b4b4375cb89b3a519"
|
|
55
55
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const rules: {
|
|
2
|
+
"@html-eslint/require-lang": string;
|
|
3
|
+
"@html-eslint/require-img-alt": string;
|
|
4
|
+
"@html-eslint/require-doctype": string;
|
|
5
|
+
"@html-eslint/require-title": string;
|
|
6
|
+
"@html-eslint/no-multiple-h1": string;
|
|
7
|
+
"@html-eslint/no-extra-spacing-attrs": string;
|
|
8
|
+
"@html-eslint/attrs-newline": string;
|
|
9
|
+
"@html-eslint/element-newline": string;
|
|
10
|
+
"@html-eslint/no-duplicate-id": string;
|
|
11
|
+
"@html-eslint/indent": string;
|
|
12
|
+
"@html-eslint/require-li-container": string;
|
|
13
|
+
"@html-eslint/quotes": string;
|
|
14
|
+
"@html-eslint/no-obsolete-tags": string;
|
|
15
|
+
"@html-eslint/require-closing-tags": string;
|
|
16
|
+
"@html-eslint/no-duplicate-attrs": string;
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=recommended.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommended.d.ts","sourceRoot":"","sources":["../../lib/configs/recommended.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../lib/constants/index.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"obsolete-tags.d.ts","sourceRoot":"","sources":["../../lib/constants/obsolete-tags.js"],"names":[],"mappings":"wBACW,MAAM,EAAE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-category.d.ts","sourceRoot":"","sources":["../../lib/constants/rule-category.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"void-elements.d.ts","sourceRoot":"","sources":["../../lib/constants/void-elements.js"],"names":[],"mappings":"wBAAW,MAAM,EAAE"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare const _exports: RuleModule;
|
|
2
|
+
export = _exports;
|
|
3
|
+
export type RuleFixer = import("../types").RuleFixer;
|
|
4
|
+
export type RuleModule = import("../types").RuleModule;
|
|
5
|
+
export type TagNode = import("../types").TagNode;
|
|
6
|
+
export type MessageId = {
|
|
7
|
+
CLOSE_STYLE_WRONG: "closeStyleWrong";
|
|
8
|
+
NEWLINE_MISSING: "newlineMissing";
|
|
9
|
+
NEWLINE_UNEXPECTED: "newlineUnexpected";
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=attrs-newline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attrs-newline.d.ts","sourceRoot":"","sources":["../../lib/rules/attrs-newline.js"],"names":[],"mappings":"wBAuBU,UAAU;;wBAtBN,OAAO,UAAU,EAAE,SAAS;yBAC5B,OAAO,UAAU,EAAE,UAAU;sBAC7B,OAAO,UAAU,EAAE,OAAO;;uBAE1B,iBAAiB;qBACjB,gBAAgB;wBAChB,mBAAmB"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
declare const _exports: RuleModule;
|
|
2
|
+
export = _exports;
|
|
3
|
+
export type RuleModule = import("../types").RuleModule;
|
|
4
|
+
export type ProgramNode = import("../types").ProgramNode;
|
|
5
|
+
export type TagNode = import("../types").TagNode;
|
|
6
|
+
export type BaseNode = import("../types").BaseNode;
|
|
7
|
+
//# sourceMappingURL=element-newline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element-newline.d.ts","sourceRoot":"","sources":["../../lib/rules/element-newline.js"],"names":[],"mappings":"wBAeU,UAAU;;yBAdN,OAAO,UAAU,EAAE,UAAU;0BAC7B,OAAO,UAAU,EAAE,WAAW;sBAC9B,OAAO,UAAU,EAAE,OAAO;uBAC1B,OAAO,UAAU,EAAE,QAAQ"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
declare const _exports: RuleModule;
|
|
2
|
+
export = _exports;
|
|
3
|
+
export type RuleModule = import("../types").RuleModule;
|
|
4
|
+
export type TagNode = import("../types").TagNode;
|
|
5
|
+
export type ScriptTagNode = import("../types").ScriptTagNode;
|
|
6
|
+
export type StyleTagNode = import("../types").StyleTagNode;
|
|
7
|
+
//# sourceMappingURL=id-naming-convention.d.ts.map
|