@html-eslint/eslint-plugin 0.13.2 → 0.14.1
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/rules/element-newline.js +21 -56
- package/lib/rules/id-naming-convention.js +5 -2
- package/lib/rules/indent.js +165 -179
- package/lib/rules/no-abstract-roles.js +2 -2
- package/lib/rules/no-accesskey-attrs.js +1 -1
- package/lib/rules/no-aria-hidden-body.js +14 -6
- package/lib/rules/no-duplicate-attrs.js +7 -7
- package/lib/rules/no-duplicate-id.js +8 -5
- package/lib/rules/no-extra-spacing-attrs.js +21 -40
- package/lib/rules/no-inline-styles.js +4 -10
- package/lib/rules/no-multiple-h1.js +4 -2
- package/lib/rules/no-non-scalable-viewport.js +8 -3
- package/lib/rules/no-obsolete-tags.js +4 -15
- package/lib/rules/no-positive-tabindex.js +6 -2
- package/lib/rules/no-restricted-attrs.js +12 -18
- package/lib/rules/no-skip-heading-levels.js +5 -2
- package/lib/rules/no-target-blank.js +8 -5
- package/lib/rules/quotes.js +29 -70
- package/lib/rules/require-button-type.js +8 -5
- package/lib/rules/require-closing-tags.js +26 -41
- package/lib/rules/require-doctype.js +1 -1
- package/lib/rules/require-frame-title.js +7 -11
- package/lib/rules/require-img-alt.js +15 -17
- package/lib/rules/require-lang.js +13 -7
- package/lib/rules/require-li-container.js +7 -11
- package/lib/rules/require-meta-charset.js +21 -17
- package/lib/rules/require-meta-description.js +30 -16
- package/lib/rules/require-meta-viewport.js +36 -23
- package/lib/rules/require-title.js +38 -16
- package/lib/rules/utils/node-utils.js +58 -20
- package/lib/types.d.ts +193 -62
- package/package.json +4 -3
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
* @typedef {import("../types").ElementNode} ElementNode
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
1
|
const { RULE_CATEGORY } = require("../constants");
|
|
7
2
|
|
|
8
3
|
const MESSAGE_IDS = {
|
|
9
4
|
MISSING_ALT: "missingAlt",
|
|
10
5
|
};
|
|
11
6
|
|
|
12
|
-
/**
|
|
13
|
-
* @type {Rule}
|
|
14
|
-
*/
|
|
15
7
|
module.exports = {
|
|
16
8
|
meta: {
|
|
17
9
|
type: "code",
|
|
@@ -31,10 +23,19 @@ module.exports = {
|
|
|
31
23
|
|
|
32
24
|
create(context) {
|
|
33
25
|
return {
|
|
34
|
-
|
|
26
|
+
Tag(node) {
|
|
27
|
+
if (node.name !== "img") {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
35
30
|
if (!hasAltAttrAndValue(node)) {
|
|
36
31
|
context.report({
|
|
37
|
-
node:
|
|
32
|
+
node: {
|
|
33
|
+
loc: {
|
|
34
|
+
start: node.openStart.loc.start,
|
|
35
|
+
end: node.openEnd.loc.end,
|
|
36
|
+
},
|
|
37
|
+
range: [node.openStart.range[0], node.openEnd.range[1]],
|
|
38
|
+
},
|
|
38
39
|
messageId: MESSAGE_IDS.MISSING_ALT,
|
|
39
40
|
});
|
|
40
41
|
}
|
|
@@ -43,13 +44,10 @@ module.exports = {
|
|
|
43
44
|
},
|
|
44
45
|
};
|
|
45
46
|
|
|
46
|
-
/**
|
|
47
|
-
* Checks whether a node has `alt` attribute value or not.
|
|
48
|
-
* @param {ElementNode} node a node to check.
|
|
49
|
-
* @returns {boolean} `true` if a node has `alt` attribute value.
|
|
50
|
-
*/
|
|
51
47
|
function hasAltAttrAndValue(node) {
|
|
52
|
-
return
|
|
53
|
-
|
|
48
|
+
return node.attributes.some((attr) => {
|
|
49
|
+
if (attr.key && attr.value) {
|
|
50
|
+
return attr.key.value === "alt" && typeof attr.value.value === "string";
|
|
51
|
+
}
|
|
54
52
|
});
|
|
55
53
|
}
|
|
@@ -10,9 +10,6 @@ const MESSAGE_IDS = {
|
|
|
10
10
|
EMPTY: "empty",
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
* @type {Rule}
|
|
15
|
-
*/
|
|
16
13
|
module.exports = {
|
|
17
14
|
meta: {
|
|
18
15
|
type: "code",
|
|
@@ -33,16 +30,25 @@ module.exports = {
|
|
|
33
30
|
|
|
34
31
|
create(context) {
|
|
35
32
|
return {
|
|
36
|
-
|
|
33
|
+
Tag(node) {
|
|
34
|
+
if (node.name !== "html") {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
37
|
const langAttr = NodeUtils.findAttr(node, "lang");
|
|
38
38
|
if (!langAttr) {
|
|
39
39
|
context.report({
|
|
40
|
-
node:
|
|
40
|
+
node: {
|
|
41
|
+
loc: {
|
|
42
|
+
start: node.openStart.loc.start,
|
|
43
|
+
end: node.openEnd.loc.end,
|
|
44
|
+
},
|
|
45
|
+
range: [node.openStart.range[0], node.openEnd.range[1]],
|
|
46
|
+
},
|
|
41
47
|
messageId: MESSAGE_IDS.MISSING,
|
|
42
48
|
});
|
|
43
|
-
} else if (langAttr.value.trim().length === 0) {
|
|
49
|
+
} else if (langAttr.value && langAttr.value.value.trim().length === 0) {
|
|
44
50
|
context.report({
|
|
45
|
-
node: node.
|
|
51
|
+
node: node.openStart,
|
|
46
52
|
messageId: MESSAGE_IDS.EMPTY,
|
|
47
53
|
});
|
|
48
54
|
}
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
* @typedef {import("../types").Rule} Rule
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
const { RULE_CATEGORY, NODE_TYPES } = require("../constants");
|
|
1
|
+
const { RULE_CATEGORY } = require("../constants");
|
|
6
2
|
|
|
7
3
|
const MESSAGE_IDS = {
|
|
8
4
|
INVALID: "invalid",
|
|
9
5
|
};
|
|
10
6
|
|
|
11
|
-
const VALID_CONTAINERS = [
|
|
7
|
+
const VALID_CONTAINERS = ["ul", "ol", "menu"];
|
|
12
8
|
|
|
13
|
-
/**
|
|
14
|
-
* @type {Rule}
|
|
15
|
-
*/
|
|
16
9
|
module.exports = {
|
|
17
10
|
meta: {
|
|
18
11
|
type: "code",
|
|
@@ -33,13 +26,16 @@ module.exports = {
|
|
|
33
26
|
|
|
34
27
|
create(context) {
|
|
35
28
|
return {
|
|
36
|
-
|
|
29
|
+
Tag(node) {
|
|
30
|
+
if (node.name !== "li") {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
37
33
|
if (!node.parent) {
|
|
38
34
|
context.report({
|
|
39
35
|
node,
|
|
40
36
|
messageId: MESSAGE_IDS.INVALID,
|
|
41
37
|
});
|
|
42
|
-
} else if (!VALID_CONTAINERS.includes(node.parent.
|
|
38
|
+
} else if (!VALID_CONTAINERS.includes(node.parent.name || "")) {
|
|
43
39
|
context.report({
|
|
44
40
|
node,
|
|
45
41
|
messageId: MESSAGE_IDS.INVALID,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import("../types").
|
|
3
|
-
* @typedef {import("../types").Context} Context
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
|
-
const { RULE_CATEGORY
|
|
5
|
+
const { RULE_CATEGORY } = require("../constants");
|
|
7
6
|
const { NodeUtils } = require("./utils");
|
|
8
7
|
|
|
9
8
|
const MESSAGE_IDS = {
|
|
@@ -11,6 +10,9 @@ const MESSAGE_IDS = {
|
|
|
11
10
|
EMPTY: "empty",
|
|
12
11
|
};
|
|
13
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @type {Rule}
|
|
15
|
+
*/
|
|
14
16
|
module.exports = {
|
|
15
17
|
meta: {
|
|
16
18
|
type: "code",
|
|
@@ -29,21 +31,21 @@ module.exports = {
|
|
|
29
31
|
},
|
|
30
32
|
},
|
|
31
33
|
|
|
32
|
-
/**
|
|
33
|
-
* @param {Context} context
|
|
34
|
-
*/
|
|
35
34
|
create(context) {
|
|
36
35
|
return {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
Tag(node) {
|
|
37
|
+
if (node.name !== "head") {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const metaCharset = node.children.find((child) => {
|
|
42
42
|
return (
|
|
43
|
-
child.type ===
|
|
43
|
+
child.type === "Tag" &&
|
|
44
|
+
child.name === "meta" &&
|
|
44
45
|
!!NodeUtils.findAttr(child, "charset")
|
|
45
46
|
);
|
|
46
47
|
});
|
|
48
|
+
|
|
47
49
|
if (!metaCharset) {
|
|
48
50
|
context.report({
|
|
49
51
|
node,
|
|
@@ -52,11 +54,13 @@ module.exports = {
|
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
54
56
|
const charsetAttr = NodeUtils.findAttr(metaCharset, "charset");
|
|
55
|
-
if (charsetAttr
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
if (charsetAttr) {
|
|
58
|
+
if (!charsetAttr.value || !charsetAttr.value.value.length) {
|
|
59
|
+
context.report({
|
|
60
|
+
node: charsetAttr,
|
|
61
|
+
messageId: MESSAGE_IDS.EMPTY,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
60
64
|
}
|
|
61
65
|
},
|
|
62
66
|
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import("../types").
|
|
3
|
-
* @typedef {import("
|
|
2
|
+
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
* @typedef {import("es-html-parser").TagNode} TagNode
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { RULE_CATEGORY
|
|
6
|
+
const { RULE_CATEGORY } = require("../constants");
|
|
7
7
|
const { NodeUtils } = require("./utils");
|
|
8
8
|
|
|
9
9
|
const MESSAGE_IDS = {
|
|
@@ -11,6 +11,17 @@ const MESSAGE_IDS = {
|
|
|
11
11
|
EMPTY: "empty",
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @param {TagNode['children'][number]} node
|
|
16
|
+
* @returns {boolean}
|
|
17
|
+
*/
|
|
18
|
+
function isMetaTagNode(node) {
|
|
19
|
+
return node.type === "Tag" && node.name === "meta";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @type {Rule}
|
|
24
|
+
*/
|
|
14
25
|
module.exports = {
|
|
15
26
|
meta: {
|
|
16
27
|
type: "code",
|
|
@@ -29,22 +40,21 @@ module.exports = {
|
|
|
29
40
|
'Unexpected empty `content` in `<meta name="description">`',
|
|
30
41
|
},
|
|
31
42
|
},
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @param {Context} context
|
|
35
|
-
*/
|
|
36
43
|
create(context) {
|
|
37
44
|
return {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const metaTags =
|
|
43
|
-
|
|
44
|
-
);
|
|
45
|
+
Tag(node) {
|
|
46
|
+
if (node.name !== "head") {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const metaTags = node.children.filter(isMetaTagNode);
|
|
50
|
+
|
|
45
51
|
const descriptionMetaTags = metaTags.filter((meta) => {
|
|
46
52
|
const nameAttr = NodeUtils.findAttr(meta, "name");
|
|
47
|
-
return
|
|
53
|
+
return (
|
|
54
|
+
!!nameAttr &&
|
|
55
|
+
nameAttr.value &&
|
|
56
|
+
nameAttr.value.value.toLowerCase() === "description"
|
|
57
|
+
);
|
|
48
58
|
});
|
|
49
59
|
|
|
50
60
|
if (descriptionMetaTags.length === 0) {
|
|
@@ -55,7 +65,11 @@ module.exports = {
|
|
|
55
65
|
} else {
|
|
56
66
|
descriptionMetaTags.forEach((meta) => {
|
|
57
67
|
const content = NodeUtils.findAttr(meta, "content");
|
|
58
|
-
if (
|
|
68
|
+
if (
|
|
69
|
+
!content ||
|
|
70
|
+
!content.value ||
|
|
71
|
+
!content.value.value.trim().length
|
|
72
|
+
) {
|
|
59
73
|
context.report({
|
|
60
74
|
node: content || meta,
|
|
61
75
|
messageId: MESSAGE_IDS.EMPTY,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import("../types").ElementNode} ElementNode
|
|
3
2
|
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
* @typedef {import("es-html-parser").TagNode} TagNode
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { RULE_CATEGORY
|
|
6
|
+
const { RULE_CATEGORY } = require("../constants");
|
|
7
7
|
const { NodeUtils } = require("./utils");
|
|
8
8
|
|
|
9
9
|
const MESSAGE_IDS = {
|
|
@@ -11,6 +11,23 @@ const MESSAGE_IDS = {
|
|
|
11
11
|
EMPTY: "empty",
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Checks whether a given node is a meta tag with viewport attribute or not.
|
|
16
|
+
* @param {TagNode['children'][number]} node A node to check
|
|
17
|
+
* @returns {node is TagNode} Return true if the given node is a meta tag with viewport attribute, otherwise false.
|
|
18
|
+
*/
|
|
19
|
+
function isMetaViewport(node) {
|
|
20
|
+
if (node.type === "Tag" && node.name === "meta") {
|
|
21
|
+
const nameAttribute = NodeUtils.findAttr(node, "name");
|
|
22
|
+
return (
|
|
23
|
+
nameAttribute &&
|
|
24
|
+
nameAttribute.value &&
|
|
25
|
+
nameAttribute.value.value.toLowerCase() === "viewport"
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
14
31
|
/**
|
|
15
32
|
* @type {Rule}
|
|
16
33
|
*/
|
|
@@ -34,20 +51,14 @@ module.exports = {
|
|
|
34
51
|
},
|
|
35
52
|
|
|
36
53
|
create(context) {
|
|
37
|
-
/**
|
|
38
|
-
* @param {ElementNode} node
|
|
39
|
-
* @returns {boolean}
|
|
40
|
-
*/
|
|
41
|
-
function isMetaViewport(node) {
|
|
42
|
-
if (node.type === NODE_TYPES.META) {
|
|
43
|
-
const nameAttr = NodeUtils.findAttr(node, "name");
|
|
44
|
-
return !!nameAttr && nameAttr.value.toLowerCase() === "viewport";
|
|
45
|
-
}
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
54
|
return {
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
Tag(node) {
|
|
56
|
+
if (node.name !== "head") {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const metaViewport = node.children.find(isMetaViewport);
|
|
61
|
+
|
|
51
62
|
if (!metaViewport) {
|
|
52
63
|
context.report({
|
|
53
64
|
node,
|
|
@@ -55,15 +66,17 @@ module.exports = {
|
|
|
55
66
|
});
|
|
56
67
|
return;
|
|
57
68
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
69
|
+
|
|
70
|
+
const contentAttribute = NodeUtils.findAttr(metaViewport, "content");
|
|
71
|
+
const isValueEmpty =
|
|
72
|
+
!contentAttribute.value || !contentAttribute.value.value.length;
|
|
73
|
+
|
|
74
|
+
if (isValueEmpty) {
|
|
75
|
+
const reportTarget = !contentAttribute.value
|
|
76
|
+
? metaViewport
|
|
77
|
+
: contentAttribute;
|
|
65
78
|
context.report({
|
|
66
|
-
node:
|
|
79
|
+
node: reportTarget,
|
|
67
80
|
messageId: MESSAGE_IDS.EMPTY,
|
|
68
81
|
});
|
|
69
82
|
}
|
|
@@ -1,15 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {import("../types").Rule} Rule
|
|
3
|
+
* @typedef {import("es-html-parser").TagNode} TagNode
|
|
4
|
+
* @typedef {import("es-html-parser").TextNode} TextNode
|
|
3
5
|
*/
|
|
4
|
-
|
|
5
|
-
const { RULE_CATEGORY, NODE_TYPES } = require("../constants");
|
|
6
|
-
const { NodeUtils } = require("./utils");
|
|
6
|
+
const { RULE_CATEGORY } = require("../constants");
|
|
7
7
|
|
|
8
8
|
const MESSAGE_IDS = {
|
|
9
9
|
MISSING_TITLE: "missing",
|
|
10
10
|
EMPTY_TITLE: "empty",
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Checks whether the node is a title TagNode.
|
|
15
|
+
* @param {TagNode['children'][number]} node A node to check
|
|
16
|
+
* @returns {node is TagNode} Returns true if the given node is a title TagNode, otherwise false
|
|
17
|
+
*/
|
|
18
|
+
function isTitleTagNode(node) {
|
|
19
|
+
return node.type === "Tag" && node.name === "title";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Checks whether the node is a TextNode that has value.
|
|
24
|
+
* @param {TagNode['children'][number]} node A node to check
|
|
25
|
+
* @returns {node is TextNode} Returns true if the given node is a TextNode with non-empty value, otherwise false
|
|
26
|
+
*/
|
|
27
|
+
function isNonEmptyTextNode(node) {
|
|
28
|
+
return node.type === "Text" && node.value.trim().length > 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
13
31
|
/**
|
|
14
32
|
* @type {Rule}
|
|
15
33
|
*/
|
|
@@ -33,25 +51,29 @@ module.exports = {
|
|
|
33
51
|
},
|
|
34
52
|
create(context) {
|
|
35
53
|
return {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
Tag(node) {
|
|
55
|
+
if (node.name !== "head") {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const titleTag = node.children.find(isTitleTagNode);
|
|
40
59
|
|
|
41
60
|
if (!titleTag) {
|
|
42
61
|
context.report({
|
|
43
62
|
node,
|
|
44
63
|
messageId: MESSAGE_IDS.MISSING_TITLE,
|
|
45
64
|
});
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (isTitleTagNode(titleTag)) {
|
|
69
|
+
const titleContentText = titleTag.children.find(isNonEmptyTextNode);
|
|
70
|
+
|
|
71
|
+
if (!titleContentText) {
|
|
72
|
+
context.report({
|
|
73
|
+
node: titleTag,
|
|
74
|
+
messageId: MESSAGE_IDS.EMPTY_TITLE,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
55
77
|
}
|
|
56
78
|
},
|
|
57
79
|
};
|
|
@@ -1,39 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @typedef {import("
|
|
3
|
-
* @typedef {import("
|
|
4
|
-
* @typedef {import("
|
|
5
|
-
* @typedef {import("
|
|
6
|
-
* @typedef {import("../../types").
|
|
7
|
-
* @typedef {import("../../types").
|
|
8
|
-
* @typedef {import("../../types").CommentNode} CommentNode
|
|
2
|
+
* @typedef {import("es-html-parser").TagNode} TagNode
|
|
3
|
+
* @typedef {import("es-html-parser").AnyNode} AnyNode
|
|
4
|
+
* @typedef {import("es-html-parser").TextNode} TextNode
|
|
5
|
+
* @typedef {import("es-html-parser").AttributeNode} AttributeNode
|
|
6
|
+
* @typedef {import("../../types").LineNode} LineNode
|
|
7
|
+
* @typedef {import("../../types").CommentContentNode} CommentContentNode
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
module.exports = {
|
|
12
|
-
|
|
13
|
-
*
|
|
14
|
-
* @param {
|
|
15
|
-
* @
|
|
16
|
-
* @return {AttrNode | void}
|
|
11
|
+
/*
|
|
12
|
+
* @param {TagNode} node
|
|
13
|
+
* @param {string} name
|
|
14
|
+
* @returns {AttributeNode | undefined}
|
|
17
15
|
*/
|
|
18
16
|
findAttr(node, name) {
|
|
19
|
-
return node
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
)
|
|
23
|
-
: undefined;
|
|
17
|
+
return node.attributes.find(
|
|
18
|
+
(attr) => attr.key && attr.key.value.toLowerCase() === name.toLowerCase()
|
|
19
|
+
);
|
|
24
20
|
},
|
|
25
21
|
/**
|
|
26
22
|
* Checks a node has attribute with the given name or not.
|
|
27
|
-
* @param {
|
|
23
|
+
* @param {TagNode} node node
|
|
28
24
|
* @param {string} name attribute name
|
|
29
25
|
* @return {boolean} `true` if the node has a attribute, otherwise `false`.
|
|
30
26
|
*/
|
|
31
27
|
hasAttr(node, name) {
|
|
32
|
-
return
|
|
28
|
+
return (
|
|
29
|
+
!!node &&
|
|
30
|
+
(node.attributes || []).some(
|
|
31
|
+
(attr) => attr.key && attr.key.value === name
|
|
32
|
+
)
|
|
33
|
+
);
|
|
33
34
|
},
|
|
34
35
|
/**
|
|
35
36
|
* Checks whether a node's all tokens are on the same line or not.
|
|
36
|
-
* @param {
|
|
37
|
+
* @param {AnyNode} node A node to check
|
|
37
38
|
* @returns {boolean} `true` if a node's tokens are on the same line, otherwise `false`.
|
|
38
39
|
*/
|
|
39
40
|
isNodeTokensOnSameLine(node) {
|
|
@@ -57,4 +58,41 @@ module.exports = {
|
|
|
57
58
|
isCommentNode(node) {
|
|
58
59
|
return !!(node && node.type === "comment");
|
|
59
60
|
},
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
*
|
|
64
|
+
* @param {TextNode | CommentContentNode} node
|
|
65
|
+
* @returns {LineNode[]}
|
|
66
|
+
*/
|
|
67
|
+
splitToLineNodes(node) {
|
|
68
|
+
let start = node.range[0];
|
|
69
|
+
let line = node.loc.start.line;
|
|
70
|
+
const startCol = node.loc.start.column;
|
|
71
|
+
|
|
72
|
+
return node.value.split("\n").map((value, index) => {
|
|
73
|
+
const columnStart = index === 0 ? startCol : 0;
|
|
74
|
+
/**
|
|
75
|
+
* @type {LineNode}
|
|
76
|
+
*/
|
|
77
|
+
const lineNode = {
|
|
78
|
+
type: "Line",
|
|
79
|
+
value,
|
|
80
|
+
range: [start, start + value.length],
|
|
81
|
+
loc: {
|
|
82
|
+
start: {
|
|
83
|
+
line,
|
|
84
|
+
column: columnStart,
|
|
85
|
+
},
|
|
86
|
+
end: {
|
|
87
|
+
line,
|
|
88
|
+
column: columnStart + value.length,
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
start += value.length + 1;
|
|
94
|
+
line += 1;
|
|
95
|
+
return lineNode;
|
|
96
|
+
});
|
|
97
|
+
},
|
|
60
98
|
};
|