@salesforce-ux/eslint-plugin-slds 0.1.4-alpha.3 → 0.1.5

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.
@@ -1,193 +0,0 @@
1
- "use strict";
2
- const node_1 = require("./utils/node");
3
- module.exports = {
4
- meta: {
5
- type: "problem",
6
- docs: {
7
- category: "Best Practices",
8
- recommended: true,
9
- },
10
- fixable: "code",
11
- schema: [],
12
- messages: {
13
- removeClass: "Remove the slds-button_icon-inverse class from the modal close button in components that use the SLDS modal blueprint.",
14
- changeVariant: "Change the variant attribute value from bare-inverse to bare in <lightning-button-icon> or <lightning-icon>.",
15
- removeVariant: "Remove the variant attribute from the <lightning-icon> component inside the <button> element.",
16
- ensureButtonClasses: "Add or move slds-button and slds-button_icon to the class attribute of the <button> element or <lightning-button-icon> component.",
17
- ensureSizeAttribute: "To size icons properly, set the size attribute ‌to large in the <lightning-icon> and <lightning-button-icon> components.",
18
- },
19
- },
20
- create(context) {
21
- function check(node) {
22
- if ((0, node_1.isAttributesEmpty)(node)) {
23
- return;
24
- }
25
- const tagName = node.name;
26
- // ✅ Scenario 1: Remove 'slds-button_icon-inverse' from <button>
27
- // (optional) when the parent of the button has class name `slds-modal`
28
- // and also button should have class `slds-modal__close`
29
- if (tagName === "button") {
30
- const classAttr = (0, node_1.findAttr)(node, "class");
31
- if (classAttr && classAttr.value) {
32
- const classList = classAttr.value.value.split(/\s+/);
33
- // ✅ Ensure button has "slds-modal__close" before proceeding
34
- if (!classList.includes("slds-modal__close")) {
35
- return; // Stop execution if the class is missing
36
- }
37
- if (classList.includes("slds-button_icon-inverse") || classList.includes("slds-button--icon-inverse")) {
38
- context.report({
39
- node,
40
- messageId: "removeClass",
41
- fix(fixer) {
42
- const newClassList = classList
43
- .filter((cls) => (cls !== "slds-button_icon-inverse" && cls !== "slds-button--icon-inverse"))
44
- .join(" ");
45
- return fixer.replaceText(classAttr, // Replace the full attribute
46
- `class="${newClassList}"` // Updated class list
47
- );
48
- },
49
- });
50
- }
51
- }
52
- }
53
- // ✅ Scenario 2: Fix <lightning-button-icon> and this should have class `slds-modal__close`
54
- if (tagName === "lightning-button-icon" || tagName === "lightning:buttonIcon") {
55
- const variantAttr = (0, node_1.findAttr)(node, "variant");
56
- const sizeAttr = (0, node_1.findAttr)(node, "size");
57
- const classAttr = (0, node_1.findAttr)(node, "class");
58
- const iconClassAttr = (0, node_1.findAttr)(node, "icon-class"); // 🔍 Check for icon-class attribute
59
- function validateClassAttr(attribute, attrName) {
60
- if (attribute && attribute.value) {
61
- const classList = attribute.value.value.split(/\s+/);
62
- // Irrespective of whether we are checking for class or icon-class we need to check whether the attribute is present or not.
63
- // ✅ Ensure "slds-modal__close" exists before proceeding
64
- if (!classAttr?.value?.value?.includes("slds-modal__close")) {
65
- return;
66
- }
67
- // ✅ Ensure "slds-modal__close" exists before proceeding
68
- // if (!classList.includes("slds-modal__close")) {
69
- // return; // Stop execution if the class is missing
70
- // }
71
- // Remove inverse classes
72
- if (classList.includes("slds-button_icon-inverse") || classList.includes("slds-button--icon-inverse")) {
73
- context.report({
74
- node,
75
- messageId: "removeClass",
76
- fix(fixer) {
77
- const newClassList = classList
78
- .filter((cls) => cls !== "slds-button_icon-inverse" && cls !== "slds-button--icon-inverse")
79
- .join(" ");
80
- return fixer.replaceText(attribute, // Replace the full attribute
81
- `${attrName}="${newClassList}"` // Correctly modifies the respective attribute
82
- );
83
- },
84
- });
85
- }
86
- // Ensure 'slds-button' and 'slds-button_icon' exist
87
- if (!classList.includes("slds-button") || !classList.includes("slds-button_icon")) {
88
- context.report({
89
- node: attribute,
90
- messageId: "ensureButtonClasses",
91
- fix(fixer) {
92
- let newClassList;
93
- if (attrName === 'icon-class') {
94
- newClassList = [
95
- ...classList.filter((cls) => cls !== "slds-button_icon-inverse"),
96
- ].join(" ");
97
- }
98
- else {
99
- newClassList = [
100
- "slds-button",
101
- "slds-button_icon",
102
- ...classList.filter((cls) => cls !== "slds-button_icon-inverse"),
103
- ].join(" ");
104
- }
105
- // const newClassList = [
106
- // "slds-button",
107
- // "slds-button_icon",
108
- // ...classList.filter((cls) => cls !== "slds-button_icon-inverse"),
109
- // ].join(" ");
110
- return fixer.replaceText(attribute.value, `${newClassList}`);
111
- },
112
- });
113
- }
114
- // Fix variant="bare-inverse" to "bare"
115
- if (variantAttr && variantAttr.value && variantAttr.value.value === "bare-inverse") {
116
- context.report({
117
- node: variantAttr,
118
- messageId: "changeVariant",
119
- fix(fixer) {
120
- return fixer.replaceText(variantAttr.value, `bare`);
121
- },
122
- });
123
- }
124
- // Ensure size="large" exists
125
- if (!sizeAttr) {
126
- context.report({
127
- node,
128
- messageId: "ensureSizeAttribute",
129
- fix(fixer) {
130
- if (variantAttr) {
131
- return fixer.insertTextAfterRange([variantAttr.range[1], variantAttr.range[1]], ' size="large"');
132
- }
133
- },
134
- });
135
- }
136
- }
137
- }
138
- // ✅ Validate `class` and `icon-class` separately, maintaining their own attribute names
139
- validateClassAttr(classAttr, "class");
140
- validateClassAttr(iconClassAttr, "icon-class");
141
- }
142
- // ✅ Scenario 3: Fix <lightning-icon> inside <button> & the class name of the parent name as button and it should have `slds-modal__close`
143
- if ((tagName === "lightning-icon" || tagName === "lightning:icon") && node.parent?.name === "button") {
144
- const parentClassAttr = (0, node_1.findAttr)(node.parent, "class");
145
- if (parentClassAttr && parentClassAttr.value) {
146
- const parentClassList = parentClassAttr.value.value.split(/\s+/);
147
- // ✅ Ensure the parent <button> has "slds-modal__close" before proceeding
148
- if (!parentClassList.includes("slds-modal__close")) {
149
- return; // Stop execution if the class is missing
150
- }
151
- const variantAttr = (0, node_1.findAttr)(node, "variant");
152
- const sizeAttr = (0, node_1.findAttr)(node, "size");
153
- // Fix variant="bare-inverse" to "bare"
154
- if (variantAttr && variantAttr.value && variantAttr.value.value === "bare-inverse") {
155
- context.report({
156
- node: variantAttr,
157
- messageId: "changeVariant",
158
- fix(fixer) {
159
- return fixer.replaceText(variantAttr.value, "bare");
160
- },
161
- });
162
- }
163
- // // Remove variant attribute completely
164
- // if (variantAttr) {
165
- // context.report({
166
- // node: variantAttr,
167
- // messageId: "removeVariant",
168
- // fix(fixer) {
169
- // return fixer.remove(variantAttr);
170
- // },
171
- // });
172
- // }
173
- //Ensure size="large" is set
174
- if (!sizeAttr) {
175
- context.report({
176
- node,
177
- messageId: "ensureSizeAttribute",
178
- fix(fixer) {
179
- //return fixer.insertTextAfter(node, ' size="large"');
180
- if (variantAttr) {
181
- return fixer.insertTextAfterRange([variantAttr.range[1], variantAttr.range[1]], ' size="large"');
182
- }
183
- },
184
- });
185
- }
186
- }
187
- }
188
- }
189
- return {
190
- Tag: check,
191
- };
192
- },
193
- };
@@ -1,17 +0,0 @@
1
- declare const _default: {
2
- meta: {
3
- type: string;
4
- docs: {
5
- category: string;
6
- recommended: boolean;
7
- };
8
- schema: any[];
9
- messages: {
10
- errorMsg: string;
11
- };
12
- };
13
- create(context: any): {
14
- Tag: (node: any) => void;
15
- };
16
- };
17
- export = _default;
@@ -1,55 +0,0 @@
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
- category: "Best Practices",
10
- recommended: true,
11
- },
12
- schema: [], // No additional options needed
13
- messages: {
14
- errorMsg: "The class {{className}} isn't available in SLDS 2. Update it to a class supported in SLDS 2. See lightningdesignsystem.com for more information.",
15
- },
16
- },
17
- create(context) {
18
- function check(node) {
19
- if ((0, node_1.isAttributesEmpty)(node)) {
20
- return;
21
- }
22
- const classAttr = (0, node_1.findAttr)(node, "class");
23
- if (classAttr && classAttr.value) {
24
- const classNames = classAttr.value.value.split(/\s+/);
25
- classNames.forEach((className) => {
26
- if (className && deprecatedClasses_1.deprecatedClasses.includes(className)) {
27
- // Find the exact location of the problematic class name
28
- const classNameStart = classAttr.value.value.indexOf(className) + 7; // 7 here is for `class= "`
29
- const classNameEnd = classNameStart + className.length;
30
- // Use the loc property to get line and column from the class attribute
31
- const startLoc = {
32
- line: classAttr.loc.start.line,
33
- column: classAttr.loc.start.column + classNameStart,
34
- };
35
- const endLoc = {
36
- line: classAttr.loc.start.line,
37
- column: classAttr.loc.start.column + classNameEnd,
38
- };
39
- context.report({
40
- node,
41
- loc: { start: startLoc, end: endLoc },
42
- data: {
43
- className,
44
- },
45
- messageId: "errorMsg",
46
- });
47
- }
48
- });
49
- }
50
- }
51
- return {
52
- Tag: check,
53
- };
54
- },
55
- };
@@ -1,79 +0,0 @@
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, };
@@ -1,197 +0,0 @@
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
- }