@salesforce-ux/eslint-plugin-slds 0.0.11 → 0.0.12-alpha.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/README.md +2 -0
- package/RULES.md +0 -0
- package/build/index.d.ts +19 -0
- package/build/index.js +18 -4127
- package/build/rules/deprecatedClasses.d.ts +5 -0
- package/build/{public/metadata/deprecatedClasses.json → rules/deprecatedClasses.js} +165 -293
- package/build/rules/enforce-bem-class.d.ts +30 -0
- package/build/rules/enforce-bem-class.js +68 -0
- package/build/rules/modal-close-button-issue.d.ts +23 -0
- package/build/rules/modal-close-button-issue.js +159 -0
- package/build/rules/no-deprecated-slds-classes.d.ts +18 -0
- package/build/rules/no-deprecated-slds-classes.js +56 -0
- package/build/rules/utils/node.d.ts +79 -0
- package/build/rules/utils/node.js +197 -0
- package/package.json +19 -29
- package/build/index.js.map +0 -1
- package/build/package.json +0 -30
- /package/{build/.eslintrc.yml → .eslintrc.yml} +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const node_1 = require("./utils/node");
|
|
3
|
+
module.exports = {
|
|
4
|
+
meta: {
|
|
5
|
+
type: "problem",
|
|
6
|
+
docs: {
|
|
7
|
+
description: "Ensure SLDS modal compliance by enforcing correct button and icon attributes.",
|
|
8
|
+
category: "Best Practices",
|
|
9
|
+
recommended: true,
|
|
10
|
+
},
|
|
11
|
+
fixable: "code",
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
removeClass: "Remove the class 'slds-button_icon-inverse' from SLDS Modal blueprints.",
|
|
15
|
+
changeVariant: "Change 'variant' attribute from 'bare-inverse' to 'bare' in <lightning-button-icon> or <lightning-icon>.",
|
|
16
|
+
removeVariant: "Remove 'variant' attribute completely in <lightning-icon> inside <button>.",
|
|
17
|
+
ensureButtonClasses: "Ensure 'slds-button' and 'slds-button_icon' are in the class attribute of <button> or <lightning-button-icon>.",
|
|
18
|
+
ensureSizeAttribute: "Ensure 'size' attribute is set to 'large' in <lightning-icon> or <lightning-button-icon> for correct icon sizing.",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
create(context) {
|
|
22
|
+
function check(node) {
|
|
23
|
+
if ((0, node_1.isAttributesEmpty)(node)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const tagName = node.name;
|
|
27
|
+
// ✅ Scenario 1: Remove 'slds-button_icon-inverse' from <button>
|
|
28
|
+
// (optional) when the parent of the button has class name `slds-modal`
|
|
29
|
+
// and also button should have class `slds-modal__close`
|
|
30
|
+
if (tagName === "button") {
|
|
31
|
+
const classAttr = (0, node_1.findAttr)(node, "class");
|
|
32
|
+
if (classAttr && classAttr.value) {
|
|
33
|
+
const classList = classAttr.value.value.split(/\s+/);
|
|
34
|
+
// ✅ Ensure button has "slds-modal__close" before proceeding
|
|
35
|
+
if (!classList.includes("slds-modal__close")) {
|
|
36
|
+
return; // Stop execution if the class is missing
|
|
37
|
+
}
|
|
38
|
+
if (classList.includes("slds-button_icon-inverse")) {
|
|
39
|
+
context.report({
|
|
40
|
+
node,
|
|
41
|
+
messageId: "removeClass",
|
|
42
|
+
fix(fixer) {
|
|
43
|
+
const newClassList = classList
|
|
44
|
+
.filter((cls) => cls !== "slds-button_icon-inverse")
|
|
45
|
+
.join(" ");
|
|
46
|
+
return fixer.replaceText(classAttr, // Replace the full attribute
|
|
47
|
+
`class="${newClassList}"` // Updated class list
|
|
48
|
+
);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// ✅ Scenario 2: Fix <lightning-button-icon> and this should have class `slds-modal__close`
|
|
55
|
+
if (tagName === "lightning-button-icon" || tagName === "lightning:buttonIcon") {
|
|
56
|
+
const variantAttr = (0, node_1.findAttr)(node, "variant");
|
|
57
|
+
const sizeAttr = (0, node_1.findAttr)(node, "size");
|
|
58
|
+
const classAttr = (0, node_1.findAttr)(node, "class");
|
|
59
|
+
if (classAttr && classAttr.value) {
|
|
60
|
+
const classList = classAttr.value.value.split(/\s+/);
|
|
61
|
+
// ✅ Ensure button has "slds-modal__close" before proceeding
|
|
62
|
+
if (!classList.includes("slds-modal__close")) {
|
|
63
|
+
return; // Stop execution if the class is missing
|
|
64
|
+
}
|
|
65
|
+
// Fix variant="bare-inverse" to "bare"
|
|
66
|
+
if (variantAttr && variantAttr.value && variantAttr.value.value === "bare-inverse") {
|
|
67
|
+
context.report({
|
|
68
|
+
node: variantAttr,
|
|
69
|
+
messageId: "changeVariant",
|
|
70
|
+
fix(fixer) {
|
|
71
|
+
return fixer.replaceText(variantAttr.value, `bare`);
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
// Ensure size="large" exists
|
|
76
|
+
if (!sizeAttr) {
|
|
77
|
+
context.report({
|
|
78
|
+
node,
|
|
79
|
+
messageId: "ensureSizeAttribute",
|
|
80
|
+
fix(fixer) {
|
|
81
|
+
//return fixer.insertTextAfter(node, ' size="large"');
|
|
82
|
+
if (variantAttr) {
|
|
83
|
+
return fixer.insertTextAfterRange([variantAttr?.range[1], variantAttr?.range[1]], ' size="large"');
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// Ensure 'slds-button' and 'slds-button_icon' are in the class attribute
|
|
89
|
+
if (classAttr && classAttr.value) {
|
|
90
|
+
const classList = classAttr.value.value.split(/\s+/);
|
|
91
|
+
if (!classList.includes("slds-button") || !classList.includes("slds-button_icon")) {
|
|
92
|
+
context.report({
|
|
93
|
+
node: classAttr,
|
|
94
|
+
messageId: "ensureButtonClasses",
|
|
95
|
+
fix(fixer) {
|
|
96
|
+
const newClassList = [
|
|
97
|
+
"slds-button",
|
|
98
|
+
"slds-button_icon",
|
|
99
|
+
...classList.filter((cls) => cls !== "slds-button_icon-inverse"),
|
|
100
|
+
].join(" ");
|
|
101
|
+
return fixer.replaceText(classAttr.value, `"${newClassList}"`);
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// ✅ Scenario 3: Fix <lightning-icon> inside <button> & the class name of the parent name as button and it should have `slds-modal__close`
|
|
109
|
+
if ((tagName === "lightning-icon" || tagName === "lightning:icon") && node.parent?.name === "button") {
|
|
110
|
+
const parentClassAttr = (0, node_1.findAttr)(node.parent, "class");
|
|
111
|
+
if (parentClassAttr && parentClassAttr.value) {
|
|
112
|
+
const parentClassList = parentClassAttr.value.value.split(/\s+/);
|
|
113
|
+
// ✅ Ensure the parent <button> has "slds-modal__close" before proceeding
|
|
114
|
+
if (!parentClassList.includes("slds-modal__close")) {
|
|
115
|
+
return; // Stop execution if the class is missing
|
|
116
|
+
}
|
|
117
|
+
const variantAttr = (0, node_1.findAttr)(node, "variant");
|
|
118
|
+
const sizeAttr = (0, node_1.findAttr)(node, "size");
|
|
119
|
+
// Fix variant="bare-inverse" to "bare"
|
|
120
|
+
if (variantAttr && variantAttr.value && variantAttr.value.value === "bare-inverse") {
|
|
121
|
+
context.report({
|
|
122
|
+
node: variantAttr,
|
|
123
|
+
messageId: "changeVariant",
|
|
124
|
+
fix(fixer) {
|
|
125
|
+
return fixer.replaceText(variantAttr.value, `"bare"`);
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
// Remove variant attribute completely
|
|
130
|
+
if (variantAttr) {
|
|
131
|
+
context.report({
|
|
132
|
+
node: variantAttr,
|
|
133
|
+
messageId: "removeVariant",
|
|
134
|
+
fix(fixer) {
|
|
135
|
+
return fixer.remove(variantAttr);
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
//Ensure size="large" is set
|
|
140
|
+
if (!sizeAttr) {
|
|
141
|
+
context.report({
|
|
142
|
+
node,
|
|
143
|
+
messageId: "ensureSizeAttribute",
|
|
144
|
+
fix(fixer) {
|
|
145
|
+
//return fixer.insertTextAfter(node, ' size="large"');
|
|
146
|
+
if (variantAttr) {
|
|
147
|
+
return fixer.insertTextAfterRange([variantAttr.range[1], variantAttr.range[1]], 'size="large"');
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
Tag: check,
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
meta: {
|
|
3
|
+
type: string;
|
|
4
|
+
docs: {
|
|
5
|
+
description: string;
|
|
6
|
+
category: string;
|
|
7
|
+
recommended: boolean;
|
|
8
|
+
};
|
|
9
|
+
schema: any[];
|
|
10
|
+
messages: {
|
|
11
|
+
deprecatedClass: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
create(context: any): {
|
|
15
|
+
Tag: (node: any) => void;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export = _default;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const node_1 = require("./utils/node");
|
|
3
|
+
//TODO: load deprecatedClasses from @salesforce-ux/metadata-slds instead of local res file.
|
|
4
|
+
const deprecatedClasses_1 = require("./deprecatedClasses");
|
|
5
|
+
module.exports = {
|
|
6
|
+
meta: {
|
|
7
|
+
type: "problem", // The rule type
|
|
8
|
+
docs: {
|
|
9
|
+
description: "Disallow usage of deprecated CSS classes",
|
|
10
|
+
category: "Best Practices",
|
|
11
|
+
recommended: true,
|
|
12
|
+
},
|
|
13
|
+
schema: [], // No additional options needed
|
|
14
|
+
messages: {
|
|
15
|
+
deprecatedClass: "The class '{{className}}' is deprecated and should not be used.",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
function check(node) {
|
|
20
|
+
if ((0, node_1.isAttributesEmpty)(node)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const classAttr = (0, node_1.findAttr)(node, "class");
|
|
24
|
+
if (classAttr && classAttr.value) {
|
|
25
|
+
const classNames = classAttr.value.value.split(/\s+/);
|
|
26
|
+
classNames.forEach((className) => {
|
|
27
|
+
if (className && deprecatedClasses_1.deprecatedClasses.includes(className)) {
|
|
28
|
+
// Find the exact location of the problematic class name
|
|
29
|
+
const classNameStart = classAttr.value.value.indexOf(className) + 7; // 7 here is for `class= "`
|
|
30
|
+
const classNameEnd = classNameStart + className.length;
|
|
31
|
+
// Use the loc property to get line and column from the class attribute
|
|
32
|
+
const startLoc = {
|
|
33
|
+
line: classAttr.loc.start.line,
|
|
34
|
+
column: classAttr.loc.start.column + classNameStart,
|
|
35
|
+
};
|
|
36
|
+
const endLoc = {
|
|
37
|
+
line: classAttr.loc.start.line,
|
|
38
|
+
column: classAttr.loc.start.column + classNameEnd,
|
|
39
|
+
};
|
|
40
|
+
context.report({
|
|
41
|
+
node,
|
|
42
|
+
loc: { start: startLoc, end: endLoc },
|
|
43
|
+
data: {
|
|
44
|
+
className,
|
|
45
|
+
},
|
|
46
|
+
messageId: "deprecatedClass",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
Tag: check,
|
|
54
|
+
};
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {TagNode | ScriptTagNode | StyleTagNode} node
|
|
3
|
+
* @param {string} key
|
|
4
|
+
* @returns {AttributeNode | undefined}
|
|
5
|
+
*/
|
|
6
|
+
declare function findAttr(node: any, key: any): any;
|
|
7
|
+
/**
|
|
8
|
+
* Checks whether a node's attributes is empty or not.
|
|
9
|
+
* @param {TagNode | ScriptTagNode | StyleTagNode} node
|
|
10
|
+
* @returns {boolean}
|
|
11
|
+
*/
|
|
12
|
+
declare function isAttributesEmpty(node: any): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Checks whether a node's all tokens are on the same line or not.
|
|
15
|
+
* @param {AnyNode} node A node to check
|
|
16
|
+
* @returns {boolean} `true` if a node's tokens are on the same line, otherwise `false`.
|
|
17
|
+
*/
|
|
18
|
+
declare function isNodeTokensOnSameLine(node: any): boolean;
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @param {Range} rangeA
|
|
22
|
+
* @param {Range} rangeB
|
|
23
|
+
* @returns {boolean}
|
|
24
|
+
*/
|
|
25
|
+
declare function isRangesOverlap(rangeA: any, rangeB: any): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* @param {(TextNode | CommentContentNode)['templates']} templates
|
|
28
|
+
* @param {Range} range
|
|
29
|
+
* @returns {boolean}
|
|
30
|
+
*/
|
|
31
|
+
declare function isOverlapWithTemplates(templates: any, range: any): any;
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @param {TextNode | CommentContentNode} node
|
|
35
|
+
* @returns {LineNode[]}
|
|
36
|
+
*/
|
|
37
|
+
declare function splitToLineNodes(node: any): any[];
|
|
38
|
+
/**
|
|
39
|
+
* Get location between two nodes.
|
|
40
|
+
* @param {BaseNode} before A node placed in before
|
|
41
|
+
* @param {BaseNode} after A node placed in after
|
|
42
|
+
* @returns {Location} location between two nodes.
|
|
43
|
+
*/
|
|
44
|
+
declare function getLocBetween(before: any, after: any): {
|
|
45
|
+
start: any;
|
|
46
|
+
end: any;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* @param {AttributeValueNode} node
|
|
50
|
+
* @return {boolean}
|
|
51
|
+
*/
|
|
52
|
+
declare function isExpressionInTemplate(node: any): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* @param {AnyNode} node
|
|
55
|
+
* @returns {node is TagNode}
|
|
56
|
+
*/
|
|
57
|
+
declare function isTag(node: any): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* @param {AnyNode} node
|
|
60
|
+
* @returns {node is CommentNode}
|
|
61
|
+
*/
|
|
62
|
+
declare function isComment(node: any): boolean;
|
|
63
|
+
/**
|
|
64
|
+
* @param {AnyNode} node
|
|
65
|
+
* @returns {node is TextNode}
|
|
66
|
+
*/
|
|
67
|
+
declare function isText(node: any): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* @param {string} source
|
|
70
|
+
* @returns {string[]}
|
|
71
|
+
*/
|
|
72
|
+
declare function codeToLines(source: any): any;
|
|
73
|
+
/**
|
|
74
|
+
*
|
|
75
|
+
* @param {AnyToken[]} tokens
|
|
76
|
+
* @returns {((CommentContentNode | TextNode)['templates'][number])[]}
|
|
77
|
+
*/
|
|
78
|
+
declare function getTemplateTokens(tokens: any): any[];
|
|
79
|
+
export { findAttr, isAttributesEmpty, isNodeTokensOnSameLine, splitToLineNodes, getLocBetween, isExpressionInTemplate, isTag, isComment, isText, isOverlapWithTemplates, codeToLines, isRangesOverlap, getTemplateTokens, };
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// THIS IS TAKEN FROM html-eslint
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.findAttr = findAttr;
|
|
5
|
+
exports.isAttributesEmpty = isAttributesEmpty;
|
|
6
|
+
exports.isNodeTokensOnSameLine = isNodeTokensOnSameLine;
|
|
7
|
+
exports.splitToLineNodes = splitToLineNodes;
|
|
8
|
+
exports.getLocBetween = getLocBetween;
|
|
9
|
+
exports.isExpressionInTemplate = isExpressionInTemplate;
|
|
10
|
+
exports.isTag = isTag;
|
|
11
|
+
exports.isComment = isComment;
|
|
12
|
+
exports.isText = isText;
|
|
13
|
+
exports.isOverlapWithTemplates = isOverlapWithTemplates;
|
|
14
|
+
exports.codeToLines = codeToLines;
|
|
15
|
+
exports.isRangesOverlap = isRangesOverlap;
|
|
16
|
+
exports.getTemplateTokens = getTemplateTokens;
|
|
17
|
+
const parser_1 = require("@html-eslint/parser");
|
|
18
|
+
/**
|
|
19
|
+
* @param {TagNode | ScriptTagNode | StyleTagNode} node
|
|
20
|
+
* @param {string} key
|
|
21
|
+
* @returns {AttributeNode | undefined}
|
|
22
|
+
*/
|
|
23
|
+
function findAttr(node, key) {
|
|
24
|
+
return node.attributes.find((attr) => attr.key && attr.key.value.toLowerCase() === key.toLowerCase());
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Checks whether a node's attributes is empty or not.
|
|
28
|
+
* @param {TagNode | ScriptTagNode | StyleTagNode} node
|
|
29
|
+
* @returns {boolean}
|
|
30
|
+
*/
|
|
31
|
+
function isAttributesEmpty(node) {
|
|
32
|
+
return !node.attributes || node.attributes.length <= 0;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Checks whether a node's all tokens are on the same line or not.
|
|
36
|
+
* @param {AnyNode} node A node to check
|
|
37
|
+
* @returns {boolean} `true` if a node's tokens are on the same line, otherwise `false`.
|
|
38
|
+
*/
|
|
39
|
+
function isNodeTokensOnSameLine(node) {
|
|
40
|
+
return node.loc.start.line === node.loc.end.line;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
*
|
|
44
|
+
* @param {Range} rangeA
|
|
45
|
+
* @param {Range} rangeB
|
|
46
|
+
* @returns {boolean}
|
|
47
|
+
*/
|
|
48
|
+
function isRangesOverlap(rangeA, rangeB) {
|
|
49
|
+
return rangeA[0] < rangeB[1] && rangeB[0] < rangeA[1];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* @param {(TextNode | CommentContentNode)['templates']} templates
|
|
53
|
+
* @param {Range} range
|
|
54
|
+
* @returns {boolean}
|
|
55
|
+
*/
|
|
56
|
+
function isOverlapWithTemplates(templates, range) {
|
|
57
|
+
return templates
|
|
58
|
+
.filter((template) => template.isTemplate)
|
|
59
|
+
.some((template) => isRangesOverlap(template.range, range));
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
*
|
|
63
|
+
* @param {TextNode | CommentContentNode} node
|
|
64
|
+
* @returns {LineNode[]}
|
|
65
|
+
*/
|
|
66
|
+
function splitToLineNodes(node) {
|
|
67
|
+
let start = node.range[0];
|
|
68
|
+
let line = node.loc.start.line;
|
|
69
|
+
const startCol = node.loc.start.column;
|
|
70
|
+
/**
|
|
71
|
+
* @type {LineNode[]}
|
|
72
|
+
*/
|
|
73
|
+
const lineNodes = [];
|
|
74
|
+
const templates = node.templates || [];
|
|
75
|
+
/**
|
|
76
|
+
*
|
|
77
|
+
* @param {import("../../types").Range} range
|
|
78
|
+
*/
|
|
79
|
+
function shouldSkipIndentCheck(range) {
|
|
80
|
+
const overlappedTemplates = templates.filter((template) => template.isTemplate && isRangesOverlap(template.range, range));
|
|
81
|
+
const isLineInTemplate = overlappedTemplates.some((template) => {
|
|
82
|
+
return template.range[0] <= range[0] && template.range[1] >= range[1];
|
|
83
|
+
});
|
|
84
|
+
if (isLineInTemplate) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
const isLineBeforeTemplate = overlappedTemplates.some((template) => {
|
|
88
|
+
return template.range[0] <= range[0] && template.range[1] <= range[1];
|
|
89
|
+
});
|
|
90
|
+
if (isLineBeforeTemplate) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
const isLineAfterTemplate = overlappedTemplates.some((template) => {
|
|
94
|
+
return template.range[1] <= range[0];
|
|
95
|
+
});
|
|
96
|
+
if (isLineAfterTemplate) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
node.value.split("\n").forEach((value, index) => {
|
|
102
|
+
const columnStart = index === 0 ? startCol : 0;
|
|
103
|
+
/**
|
|
104
|
+
* @type {import("../../types").Range}
|
|
105
|
+
*/
|
|
106
|
+
const range = [start, start + value.length];
|
|
107
|
+
const loc = {
|
|
108
|
+
start: {
|
|
109
|
+
line,
|
|
110
|
+
column: columnStart,
|
|
111
|
+
},
|
|
112
|
+
end: {
|
|
113
|
+
line,
|
|
114
|
+
column: columnStart + value.length,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* @type {LineNode}
|
|
119
|
+
*/
|
|
120
|
+
const lineNode = {
|
|
121
|
+
type: "Line",
|
|
122
|
+
value,
|
|
123
|
+
range,
|
|
124
|
+
loc,
|
|
125
|
+
skipIndentCheck: shouldSkipIndentCheck(range),
|
|
126
|
+
};
|
|
127
|
+
start += value.length + 1;
|
|
128
|
+
line += 1;
|
|
129
|
+
lineNodes.push(lineNode);
|
|
130
|
+
});
|
|
131
|
+
return lineNodes;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get location between two nodes.
|
|
135
|
+
* @param {BaseNode} before A node placed in before
|
|
136
|
+
* @param {BaseNode} after A node placed in after
|
|
137
|
+
* @returns {Location} location between two nodes.
|
|
138
|
+
*/
|
|
139
|
+
function getLocBetween(before, after) {
|
|
140
|
+
return {
|
|
141
|
+
start: before.loc.end,
|
|
142
|
+
end: after.loc.start,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* @param {AttributeValueNode} node
|
|
147
|
+
* @return {boolean}
|
|
148
|
+
*/
|
|
149
|
+
function isExpressionInTemplate(node) {
|
|
150
|
+
if (node.type === parser_1.NODE_TYPES.AttributeValue) {
|
|
151
|
+
return node.value.indexOf("${") === 0;
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* @param {AnyNode} node
|
|
157
|
+
* @returns {node is TagNode}
|
|
158
|
+
*/
|
|
159
|
+
function isTag(node) {
|
|
160
|
+
return node.type === parser_1.NODE_TYPES.Tag;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* @param {AnyNode} node
|
|
164
|
+
* @returns {node is CommentNode}
|
|
165
|
+
*/
|
|
166
|
+
function isComment(node) {
|
|
167
|
+
return node.type === parser_1.NODE_TYPES.Comment;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* @param {AnyNode} node
|
|
171
|
+
* @returns {node is TextNode}
|
|
172
|
+
*/
|
|
173
|
+
function isText(node) {
|
|
174
|
+
return node.type === parser_1.NODE_TYPES.Text;
|
|
175
|
+
}
|
|
176
|
+
const lineBreakPattern = /\r\n|[\r\n\u2028\u2029]/u;
|
|
177
|
+
const lineEndingPattern = new RegExp(lineBreakPattern.source, "gu");
|
|
178
|
+
/**
|
|
179
|
+
* @param {string} source
|
|
180
|
+
* @returns {string[]}
|
|
181
|
+
*/
|
|
182
|
+
function codeToLines(source) {
|
|
183
|
+
return source.split(lineEndingPattern);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
*
|
|
187
|
+
* @param {AnyToken[]} tokens
|
|
188
|
+
* @returns {((CommentContentNode | TextNode)['templates'][number])[]}
|
|
189
|
+
*/
|
|
190
|
+
function getTemplateTokens(tokens) {
|
|
191
|
+
return ([]
|
|
192
|
+
.concat(...tokens
|
|
193
|
+
// @ts-ignore
|
|
194
|
+
.map((token) => token["templates"] || []))
|
|
195
|
+
// @ts-ignore
|
|
196
|
+
.filter((token) => token.isTemplate));
|
|
197
|
+
}
|
package/package.json
CHANGED
|
@@ -1,30 +1,20 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
},
|
|
22
|
-
"devDependencies": {
|
|
23
|
-
"@rollup/plugin-commonjs": "^28.0.2",
|
|
24
|
-
"@rollup/plugin-node-resolve": "^16.0.0",
|
|
25
|
-
"@rollup/plugin-json": "^6.1.0",
|
|
26
|
-
"jest": "^29.7.0",
|
|
27
|
-
"rollup": "^2.79.2",
|
|
28
|
-
"rollup-plugin-terser": "^7.0.2"
|
|
29
|
-
}
|
|
30
|
-
}
|
|
2
|
+
"name": "@salesforce-ux/eslint-plugin-slds",
|
|
3
|
+
"version": "0.0.12-alpha.0",
|
|
4
|
+
"main": "build/index.js",
|
|
5
|
+
"files": [
|
|
6
|
+
"build/*",
|
|
7
|
+
"README.md",
|
|
8
|
+
"RULES.md",
|
|
9
|
+
".eslintrc.yml"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"description": "",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@html-eslint/eslint-plugin": "^0.34.0",
|
|
17
|
+
"@html-eslint/parser": "^0.34.0",
|
|
18
|
+
"eslint": "^8.0.0"
|
|
19
|
+
}
|
|
20
|
+
}
|