@discourse/lint-configs 2.39.1 → 2.41.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/eslint-rules/deprecated-imports.mjs +12 -0
- package/eslint-rules/keep-array-sorted.mjs +192 -0
- package/eslint-rules/no-discourse-computed/discourse-computed-analysis.mjs +614 -0
- package/eslint-rules/no-discourse-computed/discourse-computed-fixer.mjs +319 -0
- package/eslint-rules/no-discourse-computed.mjs +388 -0
- package/eslint-rules/utils/analyze-imports.mjs +65 -0
- package/eslint-rules/utils/property-path.mjs +78 -0
- package/eslint.mjs +6 -0
- package/package.json +1 -1
|
@@ -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
|
+
};
|