@discourse/lint-configs 2.39.1 → 2.40.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.
|
@@ -46,6 +46,18 @@ export default {
|
|
|
46
46
|
return fixer.replaceText(node.source, `"discourse/lib/get-url"`);
|
|
47
47
|
},
|
|
48
48
|
});
|
|
49
|
+
} else if (node.source.value === "discourse/lib/transformer/registry") {
|
|
50
|
+
context.report({
|
|
51
|
+
node,
|
|
52
|
+
message:
|
|
53
|
+
"Use 'discourse/lib/registry/transformers' instead of 'discourse/lib/transformer/registry'",
|
|
54
|
+
fix(fixer) {
|
|
55
|
+
return fixer.replaceText(
|
|
56
|
+
node.source,
|
|
57
|
+
`"discourse/lib/registry/transformers"`
|
|
58
|
+
);
|
|
59
|
+
},
|
|
60
|
+
});
|
|
49
61
|
} else if (
|
|
50
62
|
node.source.value === "discourse/helpers/html-safe" &&
|
|
51
63
|
node.specifiers[0]?.local.name === "htmlSafe"
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
meta: {
|
|
3
|
+
type: "suggestion",
|
|
4
|
+
docs: {
|
|
5
|
+
description: "Ensure marked arrays are sorted",
|
|
6
|
+
},
|
|
7
|
+
fixable: "code",
|
|
8
|
+
schema: [],
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
create(context) {
|
|
12
|
+
const sourceCode = context.getSourceCode();
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
ArrayExpression(node) {
|
|
16
|
+
const checkNodeForComment = (n) => {
|
|
17
|
+
const comments = sourceCode.getCommentsBefore(n);
|
|
18
|
+
return comments.some(
|
|
19
|
+
(c) => c.value.trim() === "eslint-discourse keep-array-sorted"
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
let hasSortComment = checkNodeForComment(node);
|
|
24
|
+
|
|
25
|
+
if (!hasSortComment) {
|
|
26
|
+
let statement = node;
|
|
27
|
+
while (
|
|
28
|
+
statement &&
|
|
29
|
+
!statement.type.endsWith("Statement") &&
|
|
30
|
+
statement.type !== "VariableDeclaration" &&
|
|
31
|
+
statement.type !== "ExportDefaultDeclaration" &&
|
|
32
|
+
statement.type !== "ExportNamedDeclaration"
|
|
33
|
+
) {
|
|
34
|
+
statement = statement.parent;
|
|
35
|
+
}
|
|
36
|
+
if (statement && checkNodeForComment(statement)) {
|
|
37
|
+
hasSortComment = true;
|
|
38
|
+
} else if (
|
|
39
|
+
statement &&
|
|
40
|
+
statement.parent &&
|
|
41
|
+
checkNodeForComment(statement.parent)
|
|
42
|
+
) {
|
|
43
|
+
hasSortComment = true;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!hasSortComment) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const elements = node.elements;
|
|
52
|
+
if (elements.length < 2) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check if all elements are Literals and of the same type (string or number)
|
|
57
|
+
const firstType =
|
|
58
|
+
elements[0] && elements[0].type === "Literal"
|
|
59
|
+
? typeof elements[0].value
|
|
60
|
+
: null;
|
|
61
|
+
|
|
62
|
+
if (firstType !== "string" && firstType !== "number") {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const canSort = elements.every(
|
|
67
|
+
(el) => el && el.type === "Literal" && typeof el.value === firstType
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (!canSort) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// An explicit comparator is needed because the default sort() converts elements to strings,
|
|
75
|
+
// which causes incorrect ordering for numbers (e.g., [1, 10, 2] instead of [1, 2, 10]).
|
|
76
|
+
const sortedElements = [...elements].sort((a, b) => {
|
|
77
|
+
if (a.value < b.value) {
|
|
78
|
+
return -1;
|
|
79
|
+
}
|
|
80
|
+
if (a.value > b.value) {
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
return 0;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const isSorted = elements.every(
|
|
87
|
+
(el, i) => el.value === sortedElements[i].value
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
if (!isSorted) {
|
|
91
|
+
context.report({
|
|
92
|
+
node,
|
|
93
|
+
message: "Array should be sorted.",
|
|
94
|
+
fix(fixer) {
|
|
95
|
+
const isMultiline = node.loc.start.line !== node.loc.end.line;
|
|
96
|
+
|
|
97
|
+
const line = sourceCode.lines[node.loc.start.line - 1];
|
|
98
|
+
const indent = line.match(/^\s*/)[0];
|
|
99
|
+
|
|
100
|
+
let innerIndent = "";
|
|
101
|
+
if (isMultiline && elements.length > 0) {
|
|
102
|
+
const firstElementLine =
|
|
103
|
+
sourceCode.lines[elements[0].loc.start.line - 1];
|
|
104
|
+
innerIndent = firstElementLine.match(/^\s*/)[0];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const getElementData = (el) => {
|
|
108
|
+
const text = sourceCode.getText(el);
|
|
109
|
+
const nextToken = sourceCode.getTokenAfter(el);
|
|
110
|
+
const commentsBefore = sourceCode.getCommentsBefore(el);
|
|
111
|
+
let commentsAfter = sourceCode.getCommentsAfter(el);
|
|
112
|
+
if (nextToken && nextToken.value === ",") {
|
|
113
|
+
commentsAfter = commentsAfter.concat(
|
|
114
|
+
sourceCode.getCommentsAfter(nextToken)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const sameLineCommentsAfter = commentsAfter.filter(
|
|
119
|
+
(c) => c.loc.start.line === el.loc.end.line
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const leadingComments = commentsBefore
|
|
123
|
+
.filter((c) => {
|
|
124
|
+
if (
|
|
125
|
+
c.value.trim() === "eslint-discourse keep-array-sorted"
|
|
126
|
+
) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
// If the comment is on the same line as the previous element OR the previous comma,
|
|
130
|
+
// it should be treated as a trailing comment of THAT element, not a leading of this one.
|
|
131
|
+
const prevToken = sourceCode.getTokenBefore(c);
|
|
132
|
+
if (
|
|
133
|
+
prevToken &&
|
|
134
|
+
prevToken.loc.end.line === c.loc.start.line
|
|
135
|
+
) {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
139
|
+
})
|
|
140
|
+
.map((c) => sourceCode.getText(c))
|
|
141
|
+
.join("\n" + innerIndent);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
text,
|
|
145
|
+
leadingComments: leadingComments
|
|
146
|
+
? leadingComments + "\n" + innerIndent
|
|
147
|
+
: "",
|
|
148
|
+
comment: sameLineCommentsAfter.length
|
|
149
|
+
? " " +
|
|
150
|
+
sameLineCommentsAfter
|
|
151
|
+
.map((c) => sourceCode.getText(c))
|
|
152
|
+
.join(" ")
|
|
153
|
+
: "",
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const elementToData = new Map();
|
|
158
|
+
elements.forEach((el) => {
|
|
159
|
+
elementToData.set(el, getElementData(el));
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
if (isMultiline) {
|
|
163
|
+
const lastToken = sourceCode.getLastToken(node);
|
|
164
|
+
const tokenBeforeLast = sourceCode.getTokenBefore(lastToken);
|
|
165
|
+
const hasTrailingComma = tokenBeforeLast.value === ",";
|
|
166
|
+
|
|
167
|
+
const lines = sortedElements.map((el, i) => {
|
|
168
|
+
const d = elementToData.get(el);
|
|
169
|
+
const isLast = i === elements.length - 1;
|
|
170
|
+
const comma = !isLast || hasTrailingComma ? "," : "";
|
|
171
|
+
return `${d.leadingComments}${d.text}${comma}${d.comment}`;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return fixer.replaceText(
|
|
175
|
+
node,
|
|
176
|
+
`[\n${innerIndent}${lines.join(
|
|
177
|
+
`\n${innerIndent}`
|
|
178
|
+
)}\n${indent}]`
|
|
179
|
+
);
|
|
180
|
+
} else {
|
|
181
|
+
const content = sortedElements
|
|
182
|
+
.map((el) => elementToData.get(el).text)
|
|
183
|
+
.join(", ");
|
|
184
|
+
return fixer.replaceText(node, `[${content}]`);
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
};
|
package/eslint.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import deprecatedPluginApis from "./eslint-rules/deprecated-plugin-apis.mjs";
|
|
|
17
17
|
import discourseCommonImports from "./eslint-rules/discourse-common-imports.mjs";
|
|
18
18
|
import i18nImport from "./eslint-rules/i18n-import-location.mjs";
|
|
19
19
|
import i18nT from "./eslint-rules/i18n-t.mjs";
|
|
20
|
+
import keepArraySorted from "./eslint-rules/keep-array-sorted.mjs";
|
|
20
21
|
import lineAfterImports from "./eslint-rules/line-after-imports.mjs";
|
|
21
22
|
import lineBeforeDefaultExport from "./eslint-rules/line-before-default-export.mjs";
|
|
22
23
|
import linesBetweenClassMembers from "./eslint-rules/lines-between-class-members.mjs";
|
|
@@ -122,6 +123,7 @@ export default [
|
|
|
122
123
|
rules: {
|
|
123
124
|
"i18n-import-location": i18nImport,
|
|
124
125
|
"i18n-t": i18nT,
|
|
126
|
+
"keep-array-sorted": keepArraySorted,
|
|
125
127
|
"service-inject-import": serviceInjectImport,
|
|
126
128
|
"truth-helpers-imports": truthHelpersImports,
|
|
127
129
|
"no-unused-services": noUnusedServices,
|
|
@@ -311,6 +313,7 @@ export default [
|
|
|
311
313
|
"discourse/no-route-template": ["error"],
|
|
312
314
|
"discourse/moved-packages-import-paths": ["error"],
|
|
313
315
|
"discourse/test-filename-suffix": ["error"],
|
|
316
|
+
"discourse/keep-array-sorted": ["error"],
|
|
314
317
|
},
|
|
315
318
|
},
|
|
316
319
|
{
|