oxlint-plugin-arch 0.2.4 → 0.3.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/CHANGELOG.md +20 -0
- package/README.md +264 -66
- package/dist/index.js +2498 -257
- package/package.json +4 -6
package/dist/index.js
CHANGED
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
3
3
|
|
|
4
|
-
// src/rules/
|
|
4
|
+
// src/rules/call-array-multiline.ts
|
|
5
5
|
import { defineRule } from "@oxlint/plugins";
|
|
6
6
|
|
|
7
7
|
// src/utils/helpers/ast.ts
|
|
8
|
+
var astVisitSkip = new Set([
|
|
9
|
+
"parent",
|
|
10
|
+
"type",
|
|
11
|
+
"start",
|
|
12
|
+
"end",
|
|
13
|
+
"range",
|
|
14
|
+
"loc",
|
|
15
|
+
"tokens",
|
|
16
|
+
"comments"
|
|
17
|
+
]);
|
|
8
18
|
function astIsNode(value) {
|
|
9
19
|
return value !== null && typeof value === "object" && typeof Reflect.get(value, "type") === "string";
|
|
10
20
|
}
|
|
@@ -19,11 +29,13 @@ function astVisit(node, ancestors, callback) {
|
|
|
19
29
|
return;
|
|
20
30
|
}
|
|
21
31
|
callback(node, ancestors);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
ancestors.push(node);
|
|
33
|
+
for (const key in node) {
|
|
34
|
+
if (Object.hasOwn(node, key) && !astVisitSkip.has(key)) {
|
|
35
|
+
astVisit(Reflect.get(node, key), ancestors, callback);
|
|
25
36
|
}
|
|
26
37
|
}
|
|
38
|
+
ancestors.pop();
|
|
27
39
|
}
|
|
28
40
|
function astContainsJsx(node) {
|
|
29
41
|
let found = false;
|
|
@@ -52,6 +64,17 @@ function astDirectReturnExpressions(node) {
|
|
|
52
64
|
function astStaticMemberName(node) {
|
|
53
65
|
return !node.computed && node.property.type === "Identifier" ? node.property.name : undefined;
|
|
54
66
|
}
|
|
67
|
+
function astDottedName(node) {
|
|
68
|
+
if (node.type === "Identifier") {
|
|
69
|
+
return node.name;
|
|
70
|
+
}
|
|
71
|
+
if (node.type !== "MemberExpression") {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const object = astDottedName(node.object);
|
|
75
|
+
const property = astStaticMemberName(node);
|
|
76
|
+
return object && property ? `${object}.${property}` : undefined;
|
|
77
|
+
}
|
|
55
78
|
function astCallName(node) {
|
|
56
79
|
if (node.callee.type === "Identifier") {
|
|
57
80
|
return node.callee.name;
|
|
@@ -181,7 +204,7 @@ function declarationsIsTrivialFunction(node) {
|
|
|
181
204
|
if (!node.body || node.body.type !== "BlockStatement") {
|
|
182
205
|
return false;
|
|
183
206
|
}
|
|
184
|
-
const statements = node.body.body.filter((
|
|
207
|
+
const statements = node.body.body.filter((statement) => statement.type !== "EmptyStatement" && !(statement.type === "ExpressionStatement" && statement.directive));
|
|
185
208
|
if (statements.length === 0) {
|
|
186
209
|
return true;
|
|
187
210
|
}
|
|
@@ -233,6 +256,23 @@ function declarationsCollectNamed(root, kinds) {
|
|
|
233
256
|
});
|
|
234
257
|
return result;
|
|
235
258
|
}
|
|
259
|
+
function declarationsInsideForLoop(node) {
|
|
260
|
+
for (let current = node.parent;current; current = current.parent) {
|
|
261
|
+
if (current.type === "ForStatement" || current.type === "ForInStatement" || current.type === "ForOfStatement") {
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
function declarationsNamedFromNode(node) {
|
|
268
|
+
if (node.type === "VariableDeclarator") {
|
|
269
|
+
const kind = declarationsNamedKind(node, [node.parent]);
|
|
270
|
+
return kind && node.id.type === "Identifier" && !declarationsInsideForLoop(node) ? { name: node.id.name, node, kind } : undefined;
|
|
271
|
+
}
|
|
272
|
+
const kind = declarationsNamedKind(node, []);
|
|
273
|
+
const name = "id" in node && node.id && "name" in node.id ? node.id.name : undefined;
|
|
274
|
+
return kind && name ? { name, node, kind } : undefined;
|
|
275
|
+
}
|
|
236
276
|
function declarationsCollectFunctions(root) {
|
|
237
277
|
return declarationsCollectNamed(root).flatMap((item) => {
|
|
238
278
|
if (item.node.type === "FunctionDeclaration") {
|
|
@@ -713,8 +753,301 @@ function schemasIsAllowedElement(node, allowScalars, namespaces, structuralMetho
|
|
|
713
753
|
function schemasTypeOperator(node, namespaces, operators) {
|
|
714
754
|
return node.typeName.type === "TSQualifiedName" && node.typeName.left.type === "Identifier" && namespaces.has(node.typeName.left.name) && operators.has(node.typeName.right.name) ? node.typeName.right.name : undefined;
|
|
715
755
|
}
|
|
756
|
+
// src/utils/helpers/comments.ts
|
|
757
|
+
var COMMENTS_DIRECTIVE = /^\s*(?:\/\s*<(?:reference\b|amd-[\w-]+\b)|@ts-(?:check|ignore|expect-error|nocheck)\b|(?:#|@)__(?:PURE|NO_SIDE_EFFECTS)__\b|@jsx(?:ImportSource|Runtime|Frag)?\b|@vite-ignore\b|(?:[#@]\s*)?sourceMappingURL=|(?:es|ox)lint-(?:disable|enable)|prettier-ignore|biome-ignore|[cv]8 ignore|istanbul ignore|webpack[A-Z])/;
|
|
758
|
+
var COMMENTS_DISABLE_NEXT_LINE = /^(?:eslint|oxlint)-disable-next-line\b/;
|
|
759
|
+
var COMMENTS_LEGAL_BLOCK = "\x00legal-block\x00";
|
|
760
|
+
function commentsNormalizedValue(comment) {
|
|
761
|
+
const value = (comment.type === "Block" ? comment.value.replace(/^\s*\* ?/gm, "") : comment.value).trimStart();
|
|
762
|
+
return comment.type === "Block" && comment.value.startsWith("!") ? COMMENTS_LEGAL_BLOCK + value : value;
|
|
763
|
+
}
|
|
764
|
+
function commentsIsDirective(value) {
|
|
765
|
+
return value.startsWith(COMMENTS_LEGAL_BLOCK) || COMMENTS_DIRECTIVE.test(value);
|
|
766
|
+
}
|
|
767
|
+
function commentsIsDisableNextLine(value) {
|
|
768
|
+
return COMMENTS_DISABLE_NEXT_LINE.test(value);
|
|
769
|
+
}
|
|
770
|
+
function commentsIsWhy(value) {
|
|
771
|
+
return value.startsWith("Why: ");
|
|
772
|
+
}
|
|
773
|
+
function commentsIsJsdoc(source, comment) {
|
|
774
|
+
return comment.type === "Block" && source.startsWith("/**", comment.range[0]);
|
|
775
|
+
}
|
|
776
|
+
// src/utils/helpers/layout.ts
|
|
777
|
+
var layoutIndentSchema = {
|
|
778
|
+
anyOf: [
|
|
779
|
+
{ type: "integer", minimum: 0 },
|
|
780
|
+
{ type: "string", enum: ["tab"] }
|
|
781
|
+
]
|
|
782
|
+
};
|
|
783
|
+
function layoutLineIndent(text, offset) {
|
|
784
|
+
const lineStart = text.lastIndexOf(`
|
|
785
|
+
`, offset - 1) + 1;
|
|
786
|
+
return /^[\t ]*/.exec(text.slice(lineStart, offset))?.[0] ?? "";
|
|
787
|
+
}
|
|
788
|
+
function layoutIndentUnit(indent = 2) {
|
|
789
|
+
return indent === "tab" ? "\t" : " ".repeat(indent);
|
|
790
|
+
}
|
|
791
|
+
function layoutNewline(text) {
|
|
792
|
+
return text.includes(`\r
|
|
793
|
+
`) ? `\r
|
|
794
|
+
` : `
|
|
795
|
+
`;
|
|
796
|
+
}
|
|
797
|
+
// src/rules/call-array-multiline.ts
|
|
798
|
+
function callArrayMultilineCallee(node) {
|
|
799
|
+
if (node.type === "Identifier")
|
|
800
|
+
return { name: node.name, root: node };
|
|
801
|
+
if (node.type !== "MemberExpression" || node.computed || node.property.type !== "Identifier")
|
|
802
|
+
return null;
|
|
803
|
+
const object = callArrayMultilineCallee(node.object);
|
|
804
|
+
return object ? { name: `${object.name}.${node.property.name}`, root: object.root } : null;
|
|
805
|
+
}
|
|
806
|
+
function callArrayMultilineIsShadowed(context, root) {
|
|
807
|
+
if (root.type !== "Identifier")
|
|
808
|
+
return false;
|
|
809
|
+
let scope = context.sourceCode.getScope(root);
|
|
810
|
+
while (scope) {
|
|
811
|
+
const variable = scope.set.get(root.name);
|
|
812
|
+
if (variable)
|
|
813
|
+
return variable.defs.length > 0;
|
|
814
|
+
scope = scope.upper;
|
|
815
|
+
}
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
function callArrayMultilineEdits(array, text, elementIndent, baseIndent, newline) {
|
|
819
|
+
const elements = array.elements;
|
|
820
|
+
if (elements.some((element) => element === null))
|
|
821
|
+
return null;
|
|
822
|
+
const present = elements.filter((element) => element !== null);
|
|
823
|
+
const first = present[0];
|
|
824
|
+
const last = present.at(-1);
|
|
825
|
+
if (!first || !last)
|
|
826
|
+
return null;
|
|
827
|
+
const edits = [];
|
|
828
|
+
const openingRange = [array.range[0] + 1, first.range[0]];
|
|
829
|
+
if (!/^\s*$/.test(text.slice(...openingRange)))
|
|
830
|
+
return null;
|
|
831
|
+
edits.push({ range: openingRange, text: `${newline}${elementIndent}` });
|
|
832
|
+
for (let index = 1;index < present.length; index++) {
|
|
833
|
+
const previous = present[index - 1];
|
|
834
|
+
const current = present[index];
|
|
835
|
+
const gap = text.slice(previous.range[1], current.range[0]);
|
|
836
|
+
if (!/^,\s*$/.test(gap))
|
|
837
|
+
return null;
|
|
838
|
+
edits.push({
|
|
839
|
+
range: [previous.range[1] + 1, current.range[0]],
|
|
840
|
+
text: `${newline}${elementIndent}`
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
const closingRange = [last.range[1], array.range[1] - 1];
|
|
844
|
+
const closingGap = text.slice(...closingRange);
|
|
845
|
+
if (!/^,?\s*$/.test(closingGap))
|
|
846
|
+
return null;
|
|
847
|
+
edits.push({
|
|
848
|
+
range: [closingRange[0] + (closingGap.startsWith(",") ? 1 : 0), closingRange[1]],
|
|
849
|
+
text: `${newline}${baseIndent}`
|
|
850
|
+
});
|
|
851
|
+
return edits;
|
|
852
|
+
}
|
|
853
|
+
var callArrayMultiline = defineRule({
|
|
854
|
+
meta: {
|
|
855
|
+
type: "layout",
|
|
856
|
+
fixable: "whitespace",
|
|
857
|
+
schema: [{
|
|
858
|
+
type: "object",
|
|
859
|
+
additionalProperties: false,
|
|
860
|
+
properties: {
|
|
861
|
+
callees: { type: "array", items: { type: "string" } },
|
|
862
|
+
minElements: { type: "integer", minimum: 1 },
|
|
863
|
+
indent: layoutIndentSchema
|
|
864
|
+
}
|
|
865
|
+
}],
|
|
866
|
+
messages: {
|
|
867
|
+
multiline: "Put every array element in this call on its own line."
|
|
868
|
+
}
|
|
869
|
+
},
|
|
870
|
+
createOnce(context) {
|
|
871
|
+
let callees = new Set(["Promise.all", "Promise.allSettled"]);
|
|
872
|
+
let minElements = 2;
|
|
873
|
+
let indentUnit = layoutIndentUnit();
|
|
874
|
+
let newline = `
|
|
875
|
+
`;
|
|
876
|
+
return {
|
|
877
|
+
before() {
|
|
878
|
+
const options = optionsFirst(context, {});
|
|
879
|
+
callees = new Set(options.callees ?? ["Promise.all", "Promise.allSettled"]);
|
|
880
|
+
minElements = options.minElements ?? 2;
|
|
881
|
+
indentUnit = layoutIndentUnit(options.indent);
|
|
882
|
+
newline = layoutNewline(context.sourceCode.text);
|
|
883
|
+
if (callees.size === 0)
|
|
884
|
+
return false;
|
|
885
|
+
},
|
|
886
|
+
CallExpression(node) {
|
|
887
|
+
const callee = callArrayMultilineCallee(node.callee);
|
|
888
|
+
if (!callee || !callees.has(callee.name))
|
|
889
|
+
return;
|
|
890
|
+
const array = node.arguments[0];
|
|
891
|
+
if (array?.type !== "ArrayExpression" || array.elements.length < minElements)
|
|
892
|
+
return;
|
|
893
|
+
if (array.loc.start.line !== array.loc.end.line)
|
|
894
|
+
return;
|
|
895
|
+
if (callee.root.type === "Identifier" && callee.root.name === "Promise" && callArrayMultilineIsShadowed(context, callee.root))
|
|
896
|
+
return;
|
|
897
|
+
const source = context.sourceCode;
|
|
898
|
+
const text = source.text;
|
|
899
|
+
const baseIndent = layoutLineIndent(text, array.range[0]);
|
|
900
|
+
const hasComments = source.getCommentsInside(array).length > 0;
|
|
901
|
+
const edits = hasComments ? null : callArrayMultilineEdits(array, text, baseIndent + indentUnit, baseIndent, newline);
|
|
902
|
+
context.report({
|
|
903
|
+
node: array,
|
|
904
|
+
messageId: "multiline",
|
|
905
|
+
fix: edits ? (fixer) => edits.map((edit) => fixer.replaceTextRange(edit.range, edit.text)) : undefined
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
// src/rules/chain-newline.ts
|
|
913
|
+
import { defineRule as defineRule2 } from "@oxlint/plugins";
|
|
914
|
+
function chainNewlineCollect(node) {
|
|
915
|
+
const links = [];
|
|
916
|
+
let current = node;
|
|
917
|
+
while (current.type === "CallExpression" && current.callee.type === "MemberExpression") {
|
|
918
|
+
const member = current.callee;
|
|
919
|
+
links.push({
|
|
920
|
+
call: current,
|
|
921
|
+
member,
|
|
922
|
+
methodName: !member.computed && member.property.type === "Identifier" ? member.property.name : null
|
|
923
|
+
});
|
|
924
|
+
current = member.object;
|
|
925
|
+
}
|
|
926
|
+
links.reverse();
|
|
927
|
+
return { root: current, links };
|
|
928
|
+
}
|
|
929
|
+
function chainNewlineRootIdentifier(node) {
|
|
930
|
+
let current = node;
|
|
931
|
+
while (current.type === "ChainExpression" || current.type === "ParenthesizedExpression") {
|
|
932
|
+
current = current.expression;
|
|
933
|
+
}
|
|
934
|
+
return current.type === "Identifier" ? current.name : null;
|
|
935
|
+
}
|
|
936
|
+
function chainNewlineMatches(root, links, group) {
|
|
937
|
+
if (links.length < group.minDepth)
|
|
938
|
+
return false;
|
|
939
|
+
const hasCriteria = group.methods !== undefined || group.onlyMethods !== undefined || group.rootPattern !== undefined;
|
|
940
|
+
if (!hasCriteria)
|
|
941
|
+
return true;
|
|
942
|
+
if (group.methods && links.some((link) => link.methodName !== null && group.methods?.has(link.methodName))) {
|
|
943
|
+
return true;
|
|
944
|
+
}
|
|
945
|
+
if (group.onlyMethods && links.every((link) => link.methodName !== null && group.onlyMethods?.has(link.methodName)))
|
|
946
|
+
return true;
|
|
947
|
+
const rootName = chainNewlineRootIdentifier(root);
|
|
948
|
+
return rootName !== null && group.rootPattern !== undefined && group.rootPattern.test(rootName);
|
|
949
|
+
}
|
|
950
|
+
var chainNewline = defineRule2({
|
|
951
|
+
meta: {
|
|
952
|
+
type: "layout",
|
|
953
|
+
fixable: "whitespace",
|
|
954
|
+
schema: [{
|
|
955
|
+
type: "object",
|
|
956
|
+
additionalProperties: false,
|
|
957
|
+
properties: {
|
|
958
|
+
groups: {
|
|
959
|
+
type: "array",
|
|
960
|
+
items: {
|
|
961
|
+
type: "object",
|
|
962
|
+
additionalProperties: false,
|
|
963
|
+
properties: {
|
|
964
|
+
minDepth: { type: "integer", minimum: 1 },
|
|
965
|
+
methods: { type: "array", items: { type: "string" } },
|
|
966
|
+
onlyMethods: { type: "array", items: { type: "string" } },
|
|
967
|
+
rootPattern: { type: "string" }
|
|
968
|
+
},
|
|
969
|
+
required: ["minDepth"]
|
|
970
|
+
}
|
|
971
|
+
},
|
|
972
|
+
indent: layoutIndentSchema
|
|
973
|
+
},
|
|
974
|
+
required: ["groups"]
|
|
975
|
+
}],
|
|
976
|
+
messages: {
|
|
977
|
+
newline: "Put every method in this call chain on its own line."
|
|
978
|
+
}
|
|
979
|
+
},
|
|
980
|
+
createOnce(context) {
|
|
981
|
+
let groups = [];
|
|
982
|
+
let indentUnit = layoutIndentUnit();
|
|
983
|
+
let newline = `
|
|
984
|
+
`;
|
|
985
|
+
let processed = new WeakSet;
|
|
986
|
+
return {
|
|
987
|
+
before() {
|
|
988
|
+
const options = optionsFirst(context, { groups: [] });
|
|
989
|
+
groups = (options.groups ?? []).map((group) => ({
|
|
990
|
+
minDepth: group.minDepth,
|
|
991
|
+
methods: group.methods ? new Set(group.methods) : undefined,
|
|
992
|
+
onlyMethods: group.onlyMethods ? new Set(group.onlyMethods) : undefined,
|
|
993
|
+
rootPattern: group.rootPattern === undefined ? undefined : new RegExp(group.rootPattern)
|
|
994
|
+
}));
|
|
995
|
+
indentUnit = layoutIndentUnit(options.indent);
|
|
996
|
+
newline = layoutNewline(context.sourceCode.text);
|
|
997
|
+
processed = new WeakSet;
|
|
998
|
+
if (groups.length === 0)
|
|
999
|
+
return false;
|
|
1000
|
+
},
|
|
1001
|
+
CallExpression(node) {
|
|
1002
|
+
if (processed.has(node))
|
|
1003
|
+
return;
|
|
1004
|
+
const parent = node.parent;
|
|
1005
|
+
if (parent?.type === "MemberExpression" && parent.object === node && parent.parent?.type === "CallExpression" && parent.parent.callee === parent)
|
|
1006
|
+
return;
|
|
1007
|
+
const { root, links } = chainNewlineCollect(node);
|
|
1008
|
+
for (const link of links)
|
|
1009
|
+
processed.add(link.call);
|
|
1010
|
+
if (!groups.some((group) => chainNewlineMatches(root, links, group)))
|
|
1011
|
+
return;
|
|
1012
|
+
const source = context.sourceCode;
|
|
1013
|
+
const text = source.text;
|
|
1014
|
+
const chainIndent = layoutLineIndent(text, root.range[0]) + indentUnit;
|
|
1015
|
+
const replacements = [];
|
|
1016
|
+
let fixable = true;
|
|
1017
|
+
let needsFix = false;
|
|
1018
|
+
for (const link of links) {
|
|
1019
|
+
if (link.member.computed) {
|
|
1020
|
+
fixable = false;
|
|
1021
|
+
needsFix = true;
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
const range = [link.member.object.range[1], link.member.property.range[0]];
|
|
1025
|
+
const gap = text.slice(...range);
|
|
1026
|
+
const operator = link.member.optional ? "?." : ".";
|
|
1027
|
+
const expected = `${newline}${chainIndent}${operator}`;
|
|
1028
|
+
if (source.commentsExistBetween(link.member.object, link.member.property) || gap.trim() !== operator) {
|
|
1029
|
+
fixable = false;
|
|
1030
|
+
}
|
|
1031
|
+
if (gap !== expected) {
|
|
1032
|
+
needsFix = true;
|
|
1033
|
+
replacements.push({ range, text: expected });
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
if (!needsFix)
|
|
1037
|
+
return;
|
|
1038
|
+
context.report({
|
|
1039
|
+
node,
|
|
1040
|
+
messageId: "newline",
|
|
1041
|
+
fix: fixable ? (fixer) => replacements.map((replacement) => fixer.replaceTextRange(replacement.range, replacement.text)) : undefined
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
});
|
|
1047
|
+
|
|
716
1048
|
// src/rules/declaration-name.ts
|
|
717
|
-
|
|
1049
|
+
import { defineRule as defineRule3 } from "@oxlint/plugins";
|
|
1050
|
+
var declarationName = defineRule3({
|
|
718
1051
|
meta: {
|
|
719
1052
|
type: "problem",
|
|
720
1053
|
schema: [{
|
|
@@ -741,40 +1074,53 @@ var declarationName = defineRule({
|
|
|
741
1074
|
}
|
|
742
1075
|
},
|
|
743
1076
|
createOnce(context) {
|
|
1077
|
+
let options = {};
|
|
1078
|
+
let kinds = null;
|
|
1079
|
+
let allowed = null;
|
|
1080
|
+
let expected = null;
|
|
1081
|
+
let prefixes = [];
|
|
1082
|
+
function check(node) {
|
|
1083
|
+
const item = declarationsNamedFromNode(node);
|
|
1084
|
+
if (!item || kinds && !kinds.has(item.kind) || allowed?.test(item.name)) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
const comparableName = options.normalize === "none" ? item.name : item.name.replaceAll("_", "").toLowerCase();
|
|
1088
|
+
const matches = expected ? expected.test(item.name) : prefixes.some((prefix) => comparableName.startsWith(prefix));
|
|
1089
|
+
if (matches) {
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
context.report({
|
|
1093
|
+
node: item.node,
|
|
1094
|
+
messageId: expected ? "pattern" : "prefix",
|
|
1095
|
+
data: expected ? { name: item.name, pattern: options.pattern ?? "" } : { name: item.name, prefix: prefixes.join(" or ") }
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
744
1098
|
return {
|
|
745
|
-
|
|
746
|
-
|
|
1099
|
+
before() {
|
|
1100
|
+
options = optionsFirst(context, {
|
|
747
1101
|
stem: "before-first-dot",
|
|
748
1102
|
normalize: "remove-separators"
|
|
749
1103
|
});
|
|
750
|
-
|
|
751
|
-
|
|
1104
|
+
kinds = options.kinds && options.kinds.length > 0 ? new Set(options.kinds) : null;
|
|
1105
|
+
allowed = options.allowPattern ? new RegExp(options.allowPattern) : null;
|
|
1106
|
+
expected = options.pattern ? new RegExp(options.pattern, options.flags) : null;
|
|
752
1107
|
const basename = namingFileBasename(context.filename).replace(/\.(tsx?|jsx?)$/, "");
|
|
753
|
-
const stem = namingFileStem(basename,
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
}
|
|
763
|
-
seen.add(key);
|
|
764
|
-
context.report({
|
|
765
|
-
node: item.node,
|
|
766
|
-
messageId: expected ? "pattern" : "prefix",
|
|
767
|
-
data: expected ? { name: item.name, pattern: options2.pattern ?? "" } : { name: item.name, prefix: prefixes.join(" or ") }
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
}
|
|
1108
|
+
const stem = namingFileStem(basename, options.stem, options.trailingRoles, options.roleSeparators);
|
|
1109
|
+
prefixes = namingFilePrefixes(stem, options.normalize ?? "remove-separators", options.singularize ?? "none");
|
|
1110
|
+
},
|
|
1111
|
+
FunctionDeclaration: check,
|
|
1112
|
+
ClassDeclaration: check,
|
|
1113
|
+
TSTypeAliasDeclaration: check,
|
|
1114
|
+
TSInterfaceDeclaration: check,
|
|
1115
|
+
TSEnumDeclaration: check,
|
|
1116
|
+
VariableDeclarator: check
|
|
771
1117
|
};
|
|
772
1118
|
}
|
|
773
1119
|
});
|
|
774
1120
|
|
|
775
1121
|
// src/rules/export-file-prefix.ts
|
|
776
|
-
import { defineRule as
|
|
777
|
-
var exportFilePrefix =
|
|
1122
|
+
import { defineRule as defineRule4 } from "@oxlint/plugins";
|
|
1123
|
+
var exportFilePrefix = defineRule4({
|
|
778
1124
|
meta: {
|
|
779
1125
|
type: "problem",
|
|
780
1126
|
schema: [{
|
|
@@ -797,22 +1143,22 @@ var exportFilePrefix = defineRule2({
|
|
|
797
1143
|
createOnce(context) {
|
|
798
1144
|
return {
|
|
799
1145
|
Program(program) {
|
|
800
|
-
const
|
|
1146
|
+
const options = optionsFirst(context, {
|
|
801
1147
|
stem: "before-first-dot",
|
|
802
1148
|
normalize: "remove-separators"
|
|
803
1149
|
});
|
|
804
1150
|
const basename = namingFileBasename(context.filename).replace(/\.(tsx?|jsx?)$/, "");
|
|
805
|
-
const stem = namingFileStem(basename,
|
|
806
|
-
const prefixes = namingFilePrefixes(stem,
|
|
807
|
-
const allowed =
|
|
808
|
-
const names =
|
|
1151
|
+
const stem = namingFileStem(basename, options.stem, options.trailingRoles, options.roleSeparators);
|
|
1152
|
+
const prefixes = namingFilePrefixes(stem, options.normalize ?? "remove-separators", options.singularize ?? "none");
|
|
1153
|
+
const allowed = options.allowPattern ? new RegExp(options.allowPattern) : null;
|
|
1154
|
+
const names = options.allDeclarations ? declarationsCollectNamed(program) : exportsCollect(program).map((binding) => ({
|
|
809
1155
|
name: binding.localName ?? binding.exportedName,
|
|
810
1156
|
node: binding.node
|
|
811
1157
|
}));
|
|
812
1158
|
const seen = new Set;
|
|
813
1159
|
for (const item of names) {
|
|
814
1160
|
const key = `${item.name}:${item.node.start}:${item.node.end}`;
|
|
815
|
-
const comparableName =
|
|
1161
|
+
const comparableName = options.normalize === "none" ? item.name : item.name.replaceAll("_", "").toLowerCase();
|
|
816
1162
|
if (seen.has(key) || allowed?.test(item.name) || prefixes.some((prefix) => comparableName.startsWith(prefix))) {
|
|
817
1163
|
continue;
|
|
818
1164
|
}
|
|
@@ -829,8 +1175,8 @@ var exportFilePrefix = defineRule2({
|
|
|
829
1175
|
});
|
|
830
1176
|
|
|
831
1177
|
// src/rules/export-name-pattern.ts
|
|
832
|
-
import { defineRule as
|
|
833
|
-
var exportNamePattern =
|
|
1178
|
+
import { defineRule as defineRule5 } from "@oxlint/plugins";
|
|
1179
|
+
var exportNamePattern = defineRule5({
|
|
834
1180
|
meta: {
|
|
835
1181
|
type: "problem",
|
|
836
1182
|
schema: [{
|
|
@@ -880,8 +1226,8 @@ var exportNamePattern = defineRule3({
|
|
|
880
1226
|
});
|
|
881
1227
|
|
|
882
1228
|
// src/rules/filename-export-name.ts
|
|
883
|
-
import { defineRule as
|
|
884
|
-
var filenameExportName =
|
|
1229
|
+
import { defineRule as defineRule6 } from "@oxlint/plugins";
|
|
1230
|
+
var filenameExportName = defineRule6({
|
|
885
1231
|
meta: {
|
|
886
1232
|
type: "problem",
|
|
887
1233
|
schema: [{
|
|
@@ -942,8 +1288,8 @@ var filenameExportName = defineRule4({
|
|
|
942
1288
|
});
|
|
943
1289
|
|
|
944
1290
|
// src/rules/filename-match.ts
|
|
945
|
-
import { defineRule as
|
|
946
|
-
var filenameMatch =
|
|
1291
|
+
import { defineRule as defineRule7 } from "@oxlint/plugins";
|
|
1292
|
+
var filenameMatch = defineRule7({
|
|
947
1293
|
meta: {
|
|
948
1294
|
type: "problem",
|
|
949
1295
|
schema: [{
|
|
@@ -977,8 +1323,8 @@ var filenameMatch = defineRule5({
|
|
|
977
1323
|
});
|
|
978
1324
|
|
|
979
1325
|
// src/rules/folder-prefix.ts
|
|
980
|
-
import { defineRule as
|
|
981
|
-
var folderPrefix =
|
|
1326
|
+
import { defineRule as defineRule8 } from "@oxlint/plugins";
|
|
1327
|
+
var folderPrefix = defineRule8({
|
|
982
1328
|
meta: {
|
|
983
1329
|
type: "problem",
|
|
984
1330
|
schema: [{
|
|
@@ -1025,129 +1371,345 @@ var folderPrefix = defineRule6({
|
|
|
1025
1371
|
}
|
|
1026
1372
|
});
|
|
1027
1373
|
|
|
1028
|
-
// src/rules/
|
|
1029
|
-
import { defineRule as
|
|
1030
|
-
|
|
1374
|
+
// src/rules/jsx-attributes-multiline.ts
|
|
1375
|
+
import { defineRule as defineRule9 } from "@oxlint/plugins";
|
|
1376
|
+
function jsxAttributesMultilineEdits(node, text, attributeIndent, baseIndent, newline) {
|
|
1377
|
+
const first = node.attributes[0];
|
|
1378
|
+
const last = node.attributes.at(-1);
|
|
1379
|
+
if (!first || !last)
|
|
1380
|
+
return null;
|
|
1381
|
+
const prefix = node.typeArguments ?? node.name;
|
|
1382
|
+
const openingRange = [prefix.range[1], first.range[0]];
|
|
1383
|
+
if (!/^\s*$/.test(text.slice(...openingRange)))
|
|
1384
|
+
return null;
|
|
1385
|
+
const edits = [{
|
|
1386
|
+
range: openingRange,
|
|
1387
|
+
text: `${newline}${attributeIndent}`
|
|
1388
|
+
}];
|
|
1389
|
+
for (let index = 1;index < node.attributes.length; index++) {
|
|
1390
|
+
const previous = node.attributes[index - 1];
|
|
1391
|
+
const current = node.attributes[index];
|
|
1392
|
+
const range = [previous.range[1], current.range[0]];
|
|
1393
|
+
if (!/^\s*$/.test(text.slice(...range)))
|
|
1394
|
+
return null;
|
|
1395
|
+
edits.push({ range, text: `${newline}${attributeIndent}` });
|
|
1396
|
+
}
|
|
1397
|
+
const closing = node.range[1] - (node.selfClosing ? 2 : 1);
|
|
1398
|
+
const closingRange = [last.range[1], closing];
|
|
1399
|
+
if (!/^\s*$/.test(text.slice(...closingRange)))
|
|
1400
|
+
return null;
|
|
1401
|
+
edits.push({ range: closingRange, text: `${newline}${baseIndent}` });
|
|
1402
|
+
return edits;
|
|
1403
|
+
}
|
|
1404
|
+
var jsxAttributesMultiline = defineRule9({
|
|
1031
1405
|
meta: {
|
|
1032
|
-
type: "
|
|
1406
|
+
type: "layout",
|
|
1407
|
+
fixable: "whitespace",
|
|
1033
1408
|
schema: [{
|
|
1034
1409
|
type: "object",
|
|
1035
1410
|
additionalProperties: false,
|
|
1036
1411
|
properties: {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
roleSeparators: { type: "array", items: { type: "string" } },
|
|
1041
|
-
allowTypeExports: { type: "boolean" }
|
|
1042
|
-
},
|
|
1043
|
-
required: ["names"]
|
|
1412
|
+
minAttributes: { type: "integer", minimum: 1 },
|
|
1413
|
+
indent: layoutIndentSchema
|
|
1414
|
+
}
|
|
1044
1415
|
}],
|
|
1045
1416
|
messages: {
|
|
1046
|
-
|
|
1417
|
+
multiline: "Put every JSX attribute and the closing token on its own line."
|
|
1047
1418
|
}
|
|
1048
1419
|
},
|
|
1049
1420
|
createOnce(context) {
|
|
1421
|
+
let minAttributes = 3;
|
|
1422
|
+
let indentUnit = layoutIndentUnit();
|
|
1423
|
+
let newline = `
|
|
1424
|
+
`;
|
|
1050
1425
|
return {
|
|
1051
|
-
|
|
1052
|
-
const
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1426
|
+
before() {
|
|
1427
|
+
const options = optionsFirst(context, {});
|
|
1428
|
+
minAttributes = options.minAttributes ?? 3;
|
|
1429
|
+
indentUnit = layoutIndentUnit(options.indent);
|
|
1430
|
+
newline = layoutNewline(context.sourceCode.text);
|
|
1431
|
+
},
|
|
1432
|
+
JSXOpeningElement(node) {
|
|
1433
|
+
if (node.attributes.length < minAttributes || node.loc.start.line !== node.loc.end.line)
|
|
1434
|
+
return;
|
|
1435
|
+
const source = context.sourceCode;
|
|
1436
|
+
const text = source.text;
|
|
1437
|
+
const baseIndent = layoutLineIndent(text, node.range[0]);
|
|
1438
|
+
const hasComments = source.getCommentsInside(node).length > 0;
|
|
1439
|
+
const edits = hasComments ? null : jsxAttributesMultilineEdits(node, text, baseIndent + indentUnit, baseIndent, newline);
|
|
1440
|
+
context.report({
|
|
1441
|
+
node,
|
|
1442
|
+
messageId: "multiline",
|
|
1443
|
+
fix: edits ? (fixer) => edits.map((edit) => fixer.replaceTextRange(edit.range, edit.text)) : undefined
|
|
1444
|
+
});
|
|
1069
1445
|
}
|
|
1070
1446
|
};
|
|
1071
1447
|
}
|
|
1072
1448
|
});
|
|
1073
1449
|
|
|
1074
|
-
// src/rules/
|
|
1075
|
-
import { defineRule as
|
|
1076
|
-
var
|
|
1450
|
+
// src/rules/key-value-same-line.ts
|
|
1451
|
+
import { defineRule as defineRule10 } from "@oxlint/plugins";
|
|
1452
|
+
var keyValueSameLine = defineRule10({
|
|
1077
1453
|
meta: {
|
|
1078
|
-
type: "
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
additionalProperties: false,
|
|
1082
|
-
properties: {
|
|
1083
|
-
keys: { type: "array", items: { type: "string" } },
|
|
1084
|
-
factoryPattern: { type: "string" }
|
|
1085
|
-
},
|
|
1086
|
-
required: ["keys"]
|
|
1087
|
-
}],
|
|
1454
|
+
type: "layout",
|
|
1455
|
+
fixable: "whitespace",
|
|
1456
|
+
schema: [],
|
|
1088
1457
|
messages: {
|
|
1089
|
-
|
|
1458
|
+
sameLine: "Keep the object key and the start of its value on the same line."
|
|
1090
1459
|
}
|
|
1091
1460
|
},
|
|
1092
1461
|
createOnce(context) {
|
|
1462
|
+
let source;
|
|
1093
1463
|
return {
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1464
|
+
before() {
|
|
1465
|
+
source = context.sourceCode;
|
|
1466
|
+
},
|
|
1467
|
+
Property(node) {
|
|
1468
|
+
if (node.parent.type !== "ObjectExpression" || node.shorthand || node.method || node.kind !== "init") {
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
let keyEnd = node.key.range[1];
|
|
1472
|
+
let keyEndLine = node.key.loc.end.line;
|
|
1473
|
+
if (node.computed) {
|
|
1474
|
+
const bracket = source.getTokensBetween(node.key, node.value).find((token) => token.value === "]");
|
|
1475
|
+
if (bracket) {
|
|
1476
|
+
keyEnd = bracket.range[1];
|
|
1477
|
+
keyEndLine = bracket.loc.end.line;
|
|
1108
1478
|
}
|
|
1109
1479
|
}
|
|
1480
|
+
if (keyEndLine === node.value.loc.start.line) {
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
const between = source.text.slice(keyEnd, node.value.range[0]);
|
|
1484
|
+
const commentsBetween = source.commentsExistBetween(node.key, node.value);
|
|
1485
|
+
const onlyColonAndSpace = /^[\s:]*$/.test(between) && between.includes(":");
|
|
1486
|
+
context.report({
|
|
1487
|
+
node,
|
|
1488
|
+
messageId: "sameLine",
|
|
1489
|
+
fix(fixer) {
|
|
1490
|
+
return !commentsBetween && onlyColonAndSpace ? fixer.replaceTextRange([keyEnd, node.value.range[0]], ": ") : null;
|
|
1491
|
+
}
|
|
1492
|
+
});
|
|
1110
1493
|
}
|
|
1111
1494
|
};
|
|
1112
1495
|
}
|
|
1113
1496
|
});
|
|
1114
1497
|
|
|
1115
|
-
// src/rules/no-
|
|
1116
|
-
import { defineRule as
|
|
1117
|
-
var
|
|
1498
|
+
// src/rules/no-comments.ts
|
|
1499
|
+
import { defineRule as defineRule11 } from "@oxlint/plugins";
|
|
1500
|
+
var NO_COMMENTS_LINE_GAP = /^[\t \r\n\u2028\u2029]*$/;
|
|
1501
|
+
var NO_COMMENTS_LINE_TERMINATOR = /\r\n|[\n\r\u2028\u2029]/;
|
|
1502
|
+
var NO_COMMENTS_TRAILING_LINES = /^[\t ]*(?:\r\n|[\n\r\u2028\u2029])(?:[\t ]*(?:\r\n|[\n\r\u2028\u2029]))*/;
|
|
1503
|
+
var NO_COMMENTS_HORIZONTAL_SPACE = /^[\t ]*$/;
|
|
1504
|
+
function noCommentsCreateLineIndex(text) {
|
|
1505
|
+
const starts = [0];
|
|
1506
|
+
const ends = [];
|
|
1507
|
+
for (let index = 0;index < text.length; index++) {
|
|
1508
|
+
const character = text[index];
|
|
1509
|
+
if (character === "\r") {
|
|
1510
|
+
ends.push(index);
|
|
1511
|
+
if (text[index + 1] === `
|
|
1512
|
+
`)
|
|
1513
|
+
index++;
|
|
1514
|
+
starts.push(index + 1);
|
|
1515
|
+
} else if (character === `
|
|
1516
|
+
` || character === "\u2028" || character === "\u2029") {
|
|
1517
|
+
ends.push(index);
|
|
1518
|
+
starts.push(index + 1);
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
ends.push(text.length);
|
|
1522
|
+
return { starts, ends };
|
|
1523
|
+
}
|
|
1524
|
+
function noCommentsLineNumber(lines, offset) {
|
|
1525
|
+
let low = 0;
|
|
1526
|
+
let high = lines.starts.length;
|
|
1527
|
+
while (low < high) {
|
|
1528
|
+
const middle = low + high >>> 1;
|
|
1529
|
+
if (lines.starts[middle] <= offset)
|
|
1530
|
+
low = middle + 1;
|
|
1531
|
+
else
|
|
1532
|
+
high = middle;
|
|
1533
|
+
}
|
|
1534
|
+
return low - 1;
|
|
1535
|
+
}
|
|
1536
|
+
function noCommentsLineStart(lines, offset) {
|
|
1537
|
+
return lines.starts[noCommentsLineNumber(lines, offset)];
|
|
1538
|
+
}
|
|
1539
|
+
function noCommentsLineEnd(lines, offset) {
|
|
1540
|
+
return lines.ends[noCommentsLineNumber(lines, offset)];
|
|
1541
|
+
}
|
|
1542
|
+
function noCommentsIncludesLine(lines, start, end) {
|
|
1543
|
+
let low = 0;
|
|
1544
|
+
let high = lines.length;
|
|
1545
|
+
while (low < high) {
|
|
1546
|
+
const middle = low + high >>> 1;
|
|
1547
|
+
if (lines[middle] < start)
|
|
1548
|
+
low = middle + 1;
|
|
1549
|
+
else
|
|
1550
|
+
high = middle;
|
|
1551
|
+
}
|
|
1552
|
+
return lines[low] <= end;
|
|
1553
|
+
}
|
|
1554
|
+
function noCommentsFollowsOpeningBrace(text, offset) {
|
|
1555
|
+
for (let index = offset - 1;index >= 0; index--) {
|
|
1556
|
+
const character = text[index];
|
|
1557
|
+
if (!/\s/.test(character))
|
|
1558
|
+
return character === "{";
|
|
1559
|
+
}
|
|
1560
|
+
return false;
|
|
1561
|
+
}
|
|
1562
|
+
function noCommentsGroups(comments, text) {
|
|
1563
|
+
const groups = [];
|
|
1564
|
+
for (const comment of comments) {
|
|
1565
|
+
const current = groups.at(-1);
|
|
1566
|
+
const previous = current?.at(-1);
|
|
1567
|
+
if (current && previous) {
|
|
1568
|
+
const between = text.slice(previous.range[1], comment.range[0]);
|
|
1569
|
+
if (NO_COMMENTS_LINE_GAP.test(between)) {
|
|
1570
|
+
current.push(comment);
|
|
1571
|
+
continue;
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
groups.push([comment]);
|
|
1575
|
+
}
|
|
1576
|
+
return groups;
|
|
1577
|
+
}
|
|
1578
|
+
function noCommentsJsxEmptyContainer(containers, start, end) {
|
|
1579
|
+
let low = 0;
|
|
1580
|
+
let high = containers.length;
|
|
1581
|
+
while (low < high) {
|
|
1582
|
+
const middle = low + high >>> 1;
|
|
1583
|
+
if (containers[middle].range[0] <= start)
|
|
1584
|
+
low = middle + 1;
|
|
1585
|
+
else
|
|
1586
|
+
high = middle;
|
|
1587
|
+
}
|
|
1588
|
+
const container = containers[low - 1];
|
|
1589
|
+
return container?.range[1] >= end ? container : undefined;
|
|
1590
|
+
}
|
|
1591
|
+
var noComments = defineRule11({
|
|
1118
1592
|
meta: {
|
|
1119
|
-
type: "
|
|
1593
|
+
type: "suggestion",
|
|
1594
|
+
fixable: "code",
|
|
1120
1595
|
schema: [{
|
|
1121
1596
|
type: "object",
|
|
1122
1597
|
additionalProperties: false,
|
|
1123
1598
|
properties: {
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1599
|
+
allowWhy: { type: "boolean", default: true },
|
|
1600
|
+
allowPatterns: { type: "array", items: { type: "string" } },
|
|
1601
|
+
allowJsdoc: { type: "boolean", default: false }
|
|
1127
1602
|
}
|
|
1128
1603
|
}],
|
|
1129
1604
|
messages: {
|
|
1130
|
-
|
|
1605
|
+
comment: "Remove this comment."
|
|
1131
1606
|
}
|
|
1132
1607
|
},
|
|
1133
1608
|
createOnce(context) {
|
|
1609
|
+
let allowWhy = true;
|
|
1610
|
+
let allowJsdoc = false;
|
|
1611
|
+
let allowed = [];
|
|
1134
1612
|
return {
|
|
1613
|
+
before() {
|
|
1614
|
+
const options = optionsFirst(context, {});
|
|
1615
|
+
allowWhy = options.allowWhy ?? true;
|
|
1616
|
+
allowJsdoc = options.allowJsdoc ?? false;
|
|
1617
|
+
allowed = (options.allowPatterns ?? []).map((pattern) => new RegExp(pattern));
|
|
1618
|
+
},
|
|
1135
1619
|
Program(program) {
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1620
|
+
const source = context.sourceCode;
|
|
1621
|
+
const text = source.text;
|
|
1622
|
+
const comments = source.getAllComments().filter((comment) => comment.type !== "Shebang");
|
|
1623
|
+
const removable = [];
|
|
1624
|
+
const protectedTargetLines = [];
|
|
1625
|
+
let mightContainJsxComment = false;
|
|
1626
|
+
let whyContinuation = false;
|
|
1627
|
+
let previousComment;
|
|
1628
|
+
for (const comment of comments) {
|
|
1629
|
+
const value = commentsNormalizedValue(comment);
|
|
1630
|
+
if (!mightContainJsxComment && comment.type === "Block" && noCommentsFollowsOpeningBrace(text, comment.range[0])) {
|
|
1631
|
+
mightContainJsxComment = true;
|
|
1632
|
+
}
|
|
1633
|
+
const directlyFollowsPrevious = previousComment !== undefined && comment.loc.start.line === previousComment.loc.end.line + 1 && NO_COMMENTS_LINE_GAP.test(text.slice(previousComment.range[1], comment.range[0]));
|
|
1634
|
+
const continuesWhy = comment.type === "Line" && whyContinuation && directlyFollowsPrevious;
|
|
1635
|
+
const isWhy = commentsIsWhy(value) || continuesWhy;
|
|
1636
|
+
if (commentsIsDisableNextLine(value)) {
|
|
1637
|
+
protectedTargetLines.push(comment.loc.end.line + 1);
|
|
1638
|
+
}
|
|
1639
|
+
whyContinuation = comment.type === "Line" && isWhy;
|
|
1640
|
+
previousComment = comment;
|
|
1641
|
+
if (allowWhy && isWhy)
|
|
1642
|
+
continue;
|
|
1643
|
+
if (commentsIsDirective(value))
|
|
1644
|
+
continue;
|
|
1645
|
+
if (allowJsdoc && commentsIsJsdoc(text, comment))
|
|
1145
1646
|
continue;
|
|
1647
|
+
if (allowed.some((pattern) => pattern.test(value.trim())))
|
|
1648
|
+
continue;
|
|
1649
|
+
removable.push(comment);
|
|
1650
|
+
}
|
|
1651
|
+
if (removable.length === 0)
|
|
1652
|
+
return;
|
|
1653
|
+
const lines = noCommentsCreateLineIndex(text);
|
|
1654
|
+
const jsxContainers = [];
|
|
1655
|
+
if (mightContainJsxComment) {
|
|
1656
|
+
astVisit(program, [], (node) => {
|
|
1657
|
+
if (node.type === "JSXExpressionContainer" && node.expression.type === "JSXEmptyExpression") {
|
|
1658
|
+
jsxContainers.push(node);
|
|
1659
|
+
}
|
|
1660
|
+
});
|
|
1661
|
+
jsxContainers.sort((left, right) => left.range[0] - right.range[0]);
|
|
1662
|
+
}
|
|
1663
|
+
for (const group of noCommentsGroups(removable, text)) {
|
|
1664
|
+
const first = group[0];
|
|
1665
|
+
const final = group[group.length - 1];
|
|
1666
|
+
const container = noCommentsJsxEmptyContainer(jsxContainers, first.range[0], final.range[1]);
|
|
1667
|
+
let embeddedUnicodeLineTerminator;
|
|
1668
|
+
for (const comment of group) {
|
|
1669
|
+
if (comment.type === "Block" && embeddedUnicodeLineTerminator === undefined) {
|
|
1670
|
+
embeddedUnicodeLineTerminator = text.slice(comment.range[0], comment.range[1]).match(/[\u2028\u2029]/)?.[0];
|
|
1671
|
+
}
|
|
1146
1672
|
}
|
|
1673
|
+
const reportOnly = container !== undefined || noCommentsIncludesLine(protectedTargetLines, first.loc.start.line, final.loc.end.line);
|
|
1674
|
+
const range0 = first.range[0];
|
|
1675
|
+
const range1 = final.range[1];
|
|
1147
1676
|
context.report({
|
|
1148
|
-
|
|
1149
|
-
messageId: "
|
|
1150
|
-
|
|
1677
|
+
loc: { start: first.loc.start, end: final.loc.end },
|
|
1678
|
+
messageId: "comment",
|
|
1679
|
+
...reportOnly ? {} : {
|
|
1680
|
+
fix(fixer) {
|
|
1681
|
+
const lineStart = noCommentsLineStart(lines, range0);
|
|
1682
|
+
const before = text.slice(lineStart, range0);
|
|
1683
|
+
if (NO_COMMENTS_HORIZONTAL_SPACE.test(before)) {
|
|
1684
|
+
const trailingLines = text.slice(range1).match(NO_COMMENTS_TRAILING_LINES);
|
|
1685
|
+
if (trailingLines) {
|
|
1686
|
+
const end = range1 + trailingLines[0].length;
|
|
1687
|
+
return embeddedUnicodeLineTerminator ? fixer.replaceTextRange([lineStart, end], embeddedUnicodeLineTerminator) : fixer.removeRange([lineStart, end]);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
const removed = text.slice(range0, range1);
|
|
1691
|
+
const lineBreak = removed.match(NO_COMMENTS_LINE_TERMINATOR)?.[0];
|
|
1692
|
+
let afterOnLine;
|
|
1693
|
+
if (lineBreak) {
|
|
1694
|
+
afterOnLine = text.slice(range1, noCommentsLineEnd(lines, range1));
|
|
1695
|
+
if (/\S/.test(before) && /\S/.test(afterOnLine)) {
|
|
1696
|
+
return fixer.replaceTextRange([range0, range1], lineBreak);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
if (embeddedUnicodeLineTerminator) {
|
|
1700
|
+
return fixer.replaceTextRange([range0, range1], embeddedUnicodeLineTerminator);
|
|
1701
|
+
}
|
|
1702
|
+
afterOnLine ??= text.slice(range1, noCommentsLineEnd(lines, range1));
|
|
1703
|
+
if (NO_COMMENTS_HORIZONTAL_SPACE.test(afterOnLine)) {
|
|
1704
|
+
const start = range0 - (/[\t ]*$/.exec(before)?.[0].length ?? 0);
|
|
1705
|
+
return fixer.removeRange([start, range1]);
|
|
1706
|
+
}
|
|
1707
|
+
const left = text[range0 - 1];
|
|
1708
|
+
const right = text[range1];
|
|
1709
|
+
const separator = left && right && !/\s/.test(left) && !/\s/.test(right) ? " " : "";
|
|
1710
|
+
return fixer.replaceTextRange([range0, range1], separator);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1151
1713
|
});
|
|
1152
1714
|
}
|
|
1153
1715
|
}
|
|
@@ -1155,10 +1717,219 @@ var noFileLevelHelpers = defineRule9({
|
|
|
1155
1717
|
}
|
|
1156
1718
|
});
|
|
1157
1719
|
|
|
1158
|
-
// src/rules/no-
|
|
1159
|
-
import { defineRule as
|
|
1160
|
-
|
|
1161
|
-
|
|
1720
|
+
// src/rules/no-extra-exports.ts
|
|
1721
|
+
import { defineRule as defineRule12 } from "@oxlint/plugins";
|
|
1722
|
+
var noExtraExports = defineRule12({
|
|
1723
|
+
meta: {
|
|
1724
|
+
type: "problem",
|
|
1725
|
+
schema: [{
|
|
1726
|
+
type: "object",
|
|
1727
|
+
additionalProperties: false,
|
|
1728
|
+
properties: {
|
|
1729
|
+
names: { type: "array", items: { type: "string" } },
|
|
1730
|
+
patterns: { type: "array", items: { type: "string" } },
|
|
1731
|
+
domainStem: { type: "string", enum: [...namingStemModes] },
|
|
1732
|
+
trailingRoles: { type: "array", items: { type: "string" } },
|
|
1733
|
+
roleSeparators: { type: "array", items: { type: "string" } },
|
|
1734
|
+
allowTypeExports: { type: "boolean" }
|
|
1735
|
+
},
|
|
1736
|
+
required: ["names"]
|
|
1737
|
+
}],
|
|
1738
|
+
messages: {
|
|
1739
|
+
extraExport: "Export '{{name}}' is not allowed; expected one of: {{allowed}}."
|
|
1740
|
+
}
|
|
1741
|
+
},
|
|
1742
|
+
createOnce(context) {
|
|
1743
|
+
return {
|
|
1744
|
+
Program(program) {
|
|
1745
|
+
const options = optionsFirst(context);
|
|
1746
|
+
const basename = namingFileBasename(context.filename).replace(/\.(tsx?|jsx?)$/, "");
|
|
1747
|
+
const domain = namingFileStem(basename, options.domainStem, options.trailingRoles, options.roleSeparators);
|
|
1748
|
+
const replacements = {
|
|
1749
|
+
Domain: namingPascalCase(domain),
|
|
1750
|
+
domain: namingCamelCase(domain)
|
|
1751
|
+
};
|
|
1752
|
+
const allowed = options.names.map((template) => template.replaceAll("{Domain}", replacements.Domain).replaceAll("{domain}", replacements.domain));
|
|
1753
|
+
const patterns = (options.patterns ?? []).map((template) => {
|
|
1754
|
+
const source = template.replaceAll("{Domain}", replacements.Domain.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")).replaceAll("{domain}", replacements.domain.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
1755
|
+
return new RegExp(`^(?:${source})$`);
|
|
1756
|
+
});
|
|
1757
|
+
for (const binding of exportsCollect(program)) {
|
|
1758
|
+
if (options.allowTypeExports && binding.typeOnly) {
|
|
1759
|
+
continue;
|
|
1760
|
+
}
|
|
1761
|
+
const name = binding.localName ?? binding.exportedName;
|
|
1762
|
+
if (!allowed.includes(name) && !patterns.some((pattern) => pattern.test(name))) {
|
|
1763
|
+
context.report({
|
|
1764
|
+
node: binding.node,
|
|
1765
|
+
messageId: "extraExport",
|
|
1766
|
+
data: {
|
|
1767
|
+
name,
|
|
1768
|
+
allowed: [...allowed, ...patterns.map((pattern) => pattern.toString())].join(", ")
|
|
1769
|
+
}
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
});
|
|
1777
|
+
|
|
1778
|
+
// src/rules/no-extra-factory-keys.ts
|
|
1779
|
+
import { defineRule as defineRule13 } from "@oxlint/plugins";
|
|
1780
|
+
function compileFactoryKeys(pattern, keys, requireKeys) {
|
|
1781
|
+
const allowedList = keys.concat(requireKeys.filter((key) => !keys.includes(key)));
|
|
1782
|
+
return {
|
|
1783
|
+
pattern: new RegExp(pattern),
|
|
1784
|
+
allowed: new Set(allowedList),
|
|
1785
|
+
allowedList,
|
|
1786
|
+
requireKeys
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
var noExtraFactoryKeys = defineRule13({
|
|
1790
|
+
meta: {
|
|
1791
|
+
type: "problem",
|
|
1792
|
+
schema: [{
|
|
1793
|
+
type: "object",
|
|
1794
|
+
additionalProperties: false,
|
|
1795
|
+
properties: {
|
|
1796
|
+
keys: { type: "array", items: { type: "string" } },
|
|
1797
|
+
factoryPattern: { type: "string" },
|
|
1798
|
+
requireKeys: { type: "array", items: { type: "string" } },
|
|
1799
|
+
keySets: {
|
|
1800
|
+
type: "array",
|
|
1801
|
+
items: {
|
|
1802
|
+
type: "object",
|
|
1803
|
+
additionalProperties: false,
|
|
1804
|
+
properties: {
|
|
1805
|
+
factoryPattern: { type: "string" },
|
|
1806
|
+
keys: { type: "array", items: { type: "string" } },
|
|
1807
|
+
requireKeys: { type: "array", items: { type: "string" } }
|
|
1808
|
+
},
|
|
1809
|
+
required: ["factoryPattern", "keys"]
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
}],
|
|
1814
|
+
messages: {
|
|
1815
|
+
extraKey: "Factory '{{factory}}' must not return key '{{key}}'; allowed keys: {{allowed}}.",
|
|
1816
|
+
missingKey: "Factory '{{factory}}' must return key '{{key}}'.",
|
|
1817
|
+
emptyObject: "Factory '{{factory}}' must not return an empty object."
|
|
1818
|
+
}
|
|
1819
|
+
},
|
|
1820
|
+
createOnce(context) {
|
|
1821
|
+
let keySets = [];
|
|
1822
|
+
let fallback;
|
|
1823
|
+
function resolveFactoryKeys(name) {
|
|
1824
|
+
const matched = keySets.find((item) => item.pattern.test(name));
|
|
1825
|
+
if (matched) {
|
|
1826
|
+
return matched;
|
|
1827
|
+
}
|
|
1828
|
+
if (fallback?.pattern.test(name)) {
|
|
1829
|
+
return fallback;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
return {
|
|
1833
|
+
before() {
|
|
1834
|
+
const options = optionsFirst(context, {});
|
|
1835
|
+
const { keys, factoryPattern = "^make[A-Z]", requireKeys = [], keySets: configured } = options;
|
|
1836
|
+
if (!keys && !configured) {
|
|
1837
|
+
return false;
|
|
1838
|
+
}
|
|
1839
|
+
keySets = (configured ?? []).map((item) => compileFactoryKeys(item.factoryPattern, item.keys, item.requireKeys ?? []));
|
|
1840
|
+
fallback = keys ? compileFactoryKeys(factoryPattern, keys, requireKeys) : undefined;
|
|
1841
|
+
},
|
|
1842
|
+
Program(program) {
|
|
1843
|
+
for (const factory of factoriesCollectTopLevel(program, /^/)) {
|
|
1844
|
+
const resolved = resolveFactoryKeys(factory.name);
|
|
1845
|
+
if (!resolved) {
|
|
1846
|
+
continue;
|
|
1847
|
+
}
|
|
1848
|
+
for (const object of factoriesDirectlyReturnedObjects(factory)) {
|
|
1849
|
+
const present = new Set(object.properties.flatMap((property) => {
|
|
1850
|
+
const key = factoriesObjectPropertyName(property);
|
|
1851
|
+
return key ? [key] : [];
|
|
1852
|
+
}));
|
|
1853
|
+
if (object.properties.length === 0 && resolved.requireKeys.length > 0) {
|
|
1854
|
+
context.report({
|
|
1855
|
+
node: object,
|
|
1856
|
+
messageId: "emptyObject",
|
|
1857
|
+
data: { factory: factory.name }
|
|
1858
|
+
});
|
|
1859
|
+
}
|
|
1860
|
+
for (const property of object.properties) {
|
|
1861
|
+
const key = factoriesObjectPropertyName(property);
|
|
1862
|
+
if (!key || !resolved.allowed.has(key)) {
|
|
1863
|
+
context.report({
|
|
1864
|
+
node: property,
|
|
1865
|
+
messageId: "extraKey",
|
|
1866
|
+
data: { factory: factory.name, key: key ?? "<computed or spread>", allowed: resolved.allowedList.join(", ") }
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
for (const key of resolved.requireKeys) {
|
|
1871
|
+
if (!present.has(key)) {
|
|
1872
|
+
context.report({
|
|
1873
|
+
node: object,
|
|
1874
|
+
messageId: "missingKey",
|
|
1875
|
+
data: { factory: factory.name, key }
|
|
1876
|
+
});
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
});
|
|
1885
|
+
|
|
1886
|
+
// src/rules/no-file-level-helpers.ts
|
|
1887
|
+
import { defineRule as defineRule14 } from "@oxlint/plugins";
|
|
1888
|
+
var noFileLevelHelpers = defineRule14({
|
|
1889
|
+
meta: {
|
|
1890
|
+
type: "problem",
|
|
1891
|
+
schema: [{
|
|
1892
|
+
type: "object",
|
|
1893
|
+
additionalProperties: false,
|
|
1894
|
+
properties: {
|
|
1895
|
+
allowPattern: { type: "string" },
|
|
1896
|
+
detectComponents: { type: "boolean" },
|
|
1897
|
+
hookPattern: { type: "string" }
|
|
1898
|
+
}
|
|
1899
|
+
}],
|
|
1900
|
+
messages: {
|
|
1901
|
+
helper: "File-level helper {{name}} must move inside its owning component or hook."
|
|
1902
|
+
}
|
|
1903
|
+
},
|
|
1904
|
+
createOnce(context) {
|
|
1905
|
+
return {
|
|
1906
|
+
Program(program) {
|
|
1907
|
+
const {
|
|
1908
|
+
allowPattern,
|
|
1909
|
+
detectComponents = true,
|
|
1910
|
+
hookPattern = "^use[A-Z]"
|
|
1911
|
+
} = optionsFirst(context, {});
|
|
1912
|
+
const allowed = allowPattern ? new RegExp(allowPattern) : null;
|
|
1913
|
+
const hooks = new RegExp(hookPattern);
|
|
1914
|
+
for (const candidate of declarationsFileLevelFunctionCandidates(program)) {
|
|
1915
|
+
if (detectComponents && reactComponentsIsLike(program, candidate) || hooks.test(candidate.name) || allowed?.test(candidate.name)) {
|
|
1916
|
+
continue;
|
|
1917
|
+
}
|
|
1918
|
+
context.report({
|
|
1919
|
+
node: candidate.node,
|
|
1920
|
+
messageId: "helper",
|
|
1921
|
+
data: { name: candidate.name }
|
|
1922
|
+
});
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
};
|
|
1926
|
+
}
|
|
1927
|
+
});
|
|
1928
|
+
|
|
1929
|
+
// src/rules/no-imported-type-alias.ts
|
|
1930
|
+
import { defineRule as defineRule15 } from "@oxlint/plugins";
|
|
1931
|
+
function importedAliasTarget(declaration, imports) {
|
|
1932
|
+
if (declaration?.type !== "TSTypeAliasDeclaration" || declaration.typeAnnotation.type !== "TSTypeReference" || declaration.typeAnnotation.typeName.type !== "Identifier" || !imports.has(declaration.typeAnnotation.typeName.name)) {
|
|
1162
1933
|
return;
|
|
1163
1934
|
}
|
|
1164
1935
|
return declaration.typeAnnotation.typeName.name;
|
|
@@ -1166,7 +1937,7 @@ function importedAliasTarget(declaration, imports) {
|
|
|
1166
1937
|
function specifierLocalName(specifier) {
|
|
1167
1938
|
return specifier.local.type === "Identifier" ? specifier.local.name : specifier.local.value;
|
|
1168
1939
|
}
|
|
1169
|
-
var noImportedTypeAlias =
|
|
1940
|
+
var noImportedTypeAlias = defineRule15({
|
|
1170
1941
|
meta: {
|
|
1171
1942
|
type: "problem",
|
|
1172
1943
|
schema: [],
|
|
@@ -1216,8 +1987,8 @@ var noImportedTypeAlias = defineRule10({
|
|
|
1216
1987
|
});
|
|
1217
1988
|
|
|
1218
1989
|
// src/rules/no-inline-schema-elements.ts
|
|
1219
|
-
import { defineRule as
|
|
1220
|
-
var noInlineSchemaElements =
|
|
1990
|
+
import { defineRule as defineRule16 } from "@oxlint/plugins";
|
|
1991
|
+
var noInlineSchemaElements = defineRule16({
|
|
1221
1992
|
meta: {
|
|
1222
1993
|
type: "problem",
|
|
1223
1994
|
schema: [{
|
|
@@ -1244,14 +2015,14 @@ var noInlineSchemaElements = defineRule11({
|
|
|
1244
2015
|
if (node.callee.type !== "MemberExpression") {
|
|
1245
2016
|
return;
|
|
1246
2017
|
}
|
|
1247
|
-
const
|
|
1248
|
-
const namespaces = new Set(
|
|
2018
|
+
const options = optionsFirst(context);
|
|
2019
|
+
const namespaces = new Set(options.namespaces);
|
|
1249
2020
|
const method = astStaticMemberName(node.callee);
|
|
1250
|
-
if (!schemasIsCall(node, namespaces) || !method || !
|
|
2021
|
+
if (!schemasIsCall(node, namespaces) || !method || !options.methods.includes(method)) {
|
|
1251
2022
|
return;
|
|
1252
2023
|
}
|
|
1253
2024
|
for (const element of schemasElements(node)) {
|
|
1254
|
-
if (!schemasIsAllowedElement(element,
|
|
2025
|
+
if (!schemasIsAllowedElement(element, options.allowZodScalars, namespaces, new Set(options.structuralMethods))) {
|
|
1255
2026
|
context.report({
|
|
1256
2027
|
node: element,
|
|
1257
2028
|
messageId: "inline",
|
|
@@ -1264,14 +2035,140 @@ var noInlineSchemaElements = defineRule11({
|
|
|
1264
2035
|
}
|
|
1265
2036
|
});
|
|
1266
2037
|
|
|
2038
|
+
// src/rules/no-inline-types.ts
|
|
2039
|
+
import { defineRule as defineRule17 } from "@oxlint/plugins";
|
|
2040
|
+
function parameterAnnotation(parameter) {
|
|
2041
|
+
if (parameter.type === "TSParameterProperty") {
|
|
2042
|
+
return parameterAnnotation(parameter.parameter);
|
|
2043
|
+
}
|
|
2044
|
+
if (parameter.type === "AssignmentPattern") {
|
|
2045
|
+
return parameterAnnotation(parameter.left);
|
|
2046
|
+
}
|
|
2047
|
+
return parameter.typeAnnotation;
|
|
2048
|
+
}
|
|
2049
|
+
var noInlineTypes = defineRule17({
|
|
2050
|
+
meta: {
|
|
2051
|
+
type: "problem",
|
|
2052
|
+
schema: [{
|
|
2053
|
+
type: "object",
|
|
2054
|
+
additionalProperties: false,
|
|
2055
|
+
properties: {
|
|
2056
|
+
parameters: { type: "boolean" },
|
|
2057
|
+
returns: { type: "boolean" },
|
|
2058
|
+
functionTypes: { type: "boolean" },
|
|
2059
|
+
minMembers: { type: "integer", minimum: 1 }
|
|
2060
|
+
}
|
|
2061
|
+
}],
|
|
2062
|
+
messages: {
|
|
2063
|
+
inlineType: "Use a named type instead of an inline {{kind}} type in this {{position}} annotation."
|
|
2064
|
+
}
|
|
2065
|
+
},
|
|
2066
|
+
createOnce(context) {
|
|
2067
|
+
let checkParameters = true;
|
|
2068
|
+
let checkReturns = true;
|
|
2069
|
+
let checkFunctionTypes = true;
|
|
2070
|
+
let minMembers = 1;
|
|
2071
|
+
function isInline(node) {
|
|
2072
|
+
return node.type === "TSTypeLiteral" && (minMembers === 1 || node.members.length >= minMembers) || checkFunctionTypes && (node.type === "TSFunctionType" || node.type === "TSConstructorType");
|
|
2073
|
+
}
|
|
2074
|
+
function check(annotation, position) {
|
|
2075
|
+
astVisit(annotation, [], (node, ancestors) => {
|
|
2076
|
+
if (!isInline(node) || ancestors.some(isInline)) {
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
context.report({
|
|
2080
|
+
node,
|
|
2081
|
+
messageId: "inlineType",
|
|
2082
|
+
data: { kind: node.type === "TSTypeLiteral" ? "object" : "function", position }
|
|
2083
|
+
});
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2086
|
+
function checkFunction(node) {
|
|
2087
|
+
if (checkParameters) {
|
|
2088
|
+
for (const parameter of node.params) {
|
|
2089
|
+
check(parameterAnnotation(parameter), "parameter");
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
if (checkReturns) {
|
|
2093
|
+
check(node.returnType, "return");
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
return {
|
|
2097
|
+
before() {
|
|
2098
|
+
const options = optionsFirst(context, {});
|
|
2099
|
+
checkParameters = options.parameters ?? true;
|
|
2100
|
+
checkReturns = options.returns ?? true;
|
|
2101
|
+
checkFunctionTypes = options.functionTypes ?? true;
|
|
2102
|
+
minMembers = options.minMembers ?? 1;
|
|
2103
|
+
if (!checkParameters && !checkReturns) {
|
|
2104
|
+
return false;
|
|
2105
|
+
}
|
|
2106
|
+
},
|
|
2107
|
+
FunctionDeclaration: checkFunction,
|
|
2108
|
+
FunctionExpression: checkFunction,
|
|
2109
|
+
ArrowFunctionExpression: checkFunction,
|
|
2110
|
+
TSDeclareFunction: checkFunction,
|
|
2111
|
+
TSEmptyBodyFunctionExpression: checkFunction
|
|
2112
|
+
};
|
|
2113
|
+
}
|
|
2114
|
+
});
|
|
2115
|
+
|
|
2116
|
+
// src/rules/no-literal-in.ts
|
|
2117
|
+
import { defineRule as defineRule18 } from "@oxlint/plugins";
|
|
2118
|
+
function literalKey(node) {
|
|
2119
|
+
if (node.type === "Literal" && typeof node.value === "string") {
|
|
2120
|
+
return node.value;
|
|
2121
|
+
}
|
|
2122
|
+
if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
|
|
2123
|
+
return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
var noLiteralIn = defineRule18({
|
|
2127
|
+
meta: {
|
|
2128
|
+
type: "suggestion",
|
|
2129
|
+
schema: [{
|
|
2130
|
+
type: "object",
|
|
2131
|
+
additionalProperties: false,
|
|
2132
|
+
properties: {
|
|
2133
|
+
allow: { type: "array", items: { type: "string" } }
|
|
2134
|
+
}
|
|
2135
|
+
}],
|
|
2136
|
+
defaultOptions: [{ allow: [] }],
|
|
2137
|
+
messages: {
|
|
2138
|
+
literalIn: "Avoid '{{key}}' in value; use Object.hasOwn() or a discriminated union."
|
|
2139
|
+
}
|
|
2140
|
+
},
|
|
2141
|
+
createOnce(context) {
|
|
2142
|
+
let allow = new Set;
|
|
2143
|
+
return {
|
|
2144
|
+
before() {
|
|
2145
|
+
allow = new Set(optionsFirst(context, { allow: [] }).allow);
|
|
2146
|
+
},
|
|
2147
|
+
BinaryExpression(node) {
|
|
2148
|
+
if (node.operator !== "in") {
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
const key = literalKey(node.left);
|
|
2152
|
+
if (key !== undefined && !allow.has(key)) {
|
|
2153
|
+
context.report({
|
|
2154
|
+
node: node.left,
|
|
2155
|
+
messageId: "literalIn",
|
|
2156
|
+
data: { key }
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
};
|
|
2161
|
+
}
|
|
2162
|
+
});
|
|
2163
|
+
|
|
1267
2164
|
// src/rules/no-local-schema-construction.ts
|
|
1268
|
-
import { defineRule as
|
|
2165
|
+
import { defineRule as defineRule19 } from "@oxlint/plugins";
|
|
1269
2166
|
var defaultRuntimeImportMessage = "Do not import runtime schema constructors from {{source}}; import a named schema instead.";
|
|
1270
2167
|
var defaultConstructionMessage = "Do not construct schemas locally; import a named schema instead.";
|
|
1271
2168
|
function formatMessage(template, data) {
|
|
1272
2169
|
return Object.entries(data).reduce((result, [key, value]) => result.replaceAll(`{{${key}}}`, value), template);
|
|
1273
2170
|
}
|
|
1274
|
-
var noLocalSchemaConstruction =
|
|
2171
|
+
var noLocalSchemaConstruction = defineRule19({
|
|
1275
2172
|
meta: {
|
|
1276
2173
|
type: "problem",
|
|
1277
2174
|
schema: [{
|
|
@@ -1357,34 +2254,342 @@ var noLocalSchemaConstruction = defineRule12({
|
|
|
1357
2254
|
if (!schemasIsRuntimeReexport(node, new Set(packages))) {
|
|
1358
2255
|
return;
|
|
1359
2256
|
}
|
|
1360
|
-
context.report({
|
|
1361
|
-
node,
|
|
1362
|
-
messageId: "runtimeImport",
|
|
1363
|
-
data: {
|
|
1364
|
-
message: formatMessage(runtimeImportMessage ?? message ?? defaultRuntimeImportMessage, { source: node.source.value })
|
|
1365
|
-
}
|
|
1366
|
-
});
|
|
2257
|
+
context.report({
|
|
2258
|
+
node,
|
|
2259
|
+
messageId: "runtimeImport",
|
|
2260
|
+
data: {
|
|
2261
|
+
message: formatMessage(runtimeImportMessage ?? message ?? defaultRuntimeImportMessage, { source: node.source.value })
|
|
2262
|
+
}
|
|
2263
|
+
});
|
|
2264
|
+
},
|
|
2265
|
+
CallExpression(node) {
|
|
2266
|
+
if (!schemasIsConstruction(node, constructors)) {
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
const { message, constructionMessage } = optionsFirst(context, {});
|
|
2270
|
+
context.report({
|
|
2271
|
+
node,
|
|
2272
|
+
messageId: "construction",
|
|
2273
|
+
data: {
|
|
2274
|
+
message: formatMessage(constructionMessage ?? message ?? defaultConstructionMessage, {})
|
|
2275
|
+
}
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
});
|
|
2281
|
+
|
|
2282
|
+
// src/rules/no-member-comments.ts
|
|
2283
|
+
import { defineRule as defineRule20 } from "@oxlint/plugins";
|
|
2284
|
+
var noMemberComments = defineRule20({
|
|
2285
|
+
meta: {
|
|
2286
|
+
type: "suggestion",
|
|
2287
|
+
fixable: "code",
|
|
2288
|
+
schema: [{
|
|
2289
|
+
type: "object",
|
|
2290
|
+
additionalProperties: false,
|
|
2291
|
+
properties: {
|
|
2292
|
+
allowWhy: { type: "boolean", default: true }
|
|
2293
|
+
}
|
|
2294
|
+
}],
|
|
2295
|
+
messages: {
|
|
2296
|
+
memberComment: "Remove this comment from the type member."
|
|
2297
|
+
}
|
|
2298
|
+
},
|
|
2299
|
+
createOnce(context) {
|
|
2300
|
+
const reported = new Set;
|
|
2301
|
+
function checkMember(node) {
|
|
2302
|
+
const parent = node.parent;
|
|
2303
|
+
if (parent.type !== "TSInterfaceBody" && parent.type !== "TSTypeLiteral" && parent.type !== "ObjectExpression")
|
|
2304
|
+
return;
|
|
2305
|
+
const source = context.sourceCode;
|
|
2306
|
+
const { allowWhy = true } = optionsFirst(context, {});
|
|
2307
|
+
const previousToken = source.getTokenBefore(node);
|
|
2308
|
+
const comments = [
|
|
2309
|
+
...source.getCommentsBefore(node).filter((comment) => !previousToken || previousToken.range[0] === parent.range[0] || previousToken.loc.end.line !== comment.loc.start.line),
|
|
2310
|
+
...source.getCommentsAfter(node).filter((comment) => comment.loc.start.line === node.loc.end.line)
|
|
2311
|
+
].filter((comment) => comment.range[0] > parent.range[0] && comment.range[1] < parent.range[1]);
|
|
2312
|
+
let whyContinuation = false;
|
|
2313
|
+
let previousLine = -1;
|
|
2314
|
+
const removable = [];
|
|
2315
|
+
for (const comment of comments) {
|
|
2316
|
+
const value = commentsNormalizedValue(comment);
|
|
2317
|
+
const isWhy = commentsIsWhy(value) || comment.type === "Line" && whyContinuation && comment.loc.start.line === previousLine + 1;
|
|
2318
|
+
whyContinuation = comment.type === "Line" && isWhy;
|
|
2319
|
+
previousLine = comment.loc.end.line;
|
|
2320
|
+
if (reported.has(comment.range[0]))
|
|
2321
|
+
continue;
|
|
2322
|
+
reported.add(comment.range[0]);
|
|
2323
|
+
if (allowWhy && isWhy)
|
|
2324
|
+
continue;
|
|
2325
|
+
if (commentsIsDirective(value))
|
|
2326
|
+
continue;
|
|
2327
|
+
removable.push(comment);
|
|
2328
|
+
}
|
|
2329
|
+
for (let index = 0;index < removable.length; ) {
|
|
2330
|
+
let last = index;
|
|
2331
|
+
while (last + 1 < removable.length && removable[last + 1].loc.start.line === removable[last].loc.end.line + 1)
|
|
2332
|
+
last++;
|
|
2333
|
+
const first = removable[index];
|
|
2334
|
+
const final = removable[last];
|
|
2335
|
+
index = last + 1;
|
|
2336
|
+
context.report({
|
|
2337
|
+
loc: { start: first.loc.start, end: final.loc.end },
|
|
2338
|
+
messageId: "memberComment",
|
|
2339
|
+
fix(fixer) {
|
|
2340
|
+
const text = source.text;
|
|
2341
|
+
const lineStart = text.lastIndexOf(`
|
|
2342
|
+
`, first.range[0] - 1) + 1;
|
|
2343
|
+
const before = text.slice(lineStart, first.range[0]);
|
|
2344
|
+
const start = first.range[0] - (/[\t ]*$/.exec(before)?.[0].length ?? 0);
|
|
2345
|
+
const after = text.slice(final.range[1]);
|
|
2346
|
+
const trailingLines = after.match(/^[\t ]*\r?\n(?:[\t ]*\r?\n)*/);
|
|
2347
|
+
if (trailingLines) {
|
|
2348
|
+
const end = final.range[1] + trailingLines[0].length;
|
|
2349
|
+
if (/^[\t ]*$/.test(before))
|
|
2350
|
+
return fixer.removeRange([lineStart, end]);
|
|
2351
|
+
const newline = trailingLines[0].includes(`\r
|
|
2352
|
+
`) ? `\r
|
|
2353
|
+
` : `
|
|
2354
|
+
`;
|
|
2355
|
+
return fixer.replaceTextRange([start, end], newline);
|
|
2356
|
+
}
|
|
2357
|
+
return fixer.removeRange([start, final.range[1]]);
|
|
2358
|
+
}
|
|
2359
|
+
});
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
return {
|
|
2363
|
+
Program() {
|
|
2364
|
+
reported.clear();
|
|
2365
|
+
},
|
|
2366
|
+
TSPropertySignature: checkMember,
|
|
2367
|
+
TSMethodSignature: checkMember,
|
|
2368
|
+
TSIndexSignature: checkMember,
|
|
2369
|
+
TSCallSignatureDeclaration: checkMember,
|
|
2370
|
+
TSConstructSignatureDeclaration: checkMember,
|
|
2371
|
+
Property: checkMember,
|
|
2372
|
+
SpreadElement: checkMember
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
});
|
|
2376
|
+
|
|
2377
|
+
// src/rules/no-module-mutable-state.ts
|
|
2378
|
+
import { defineRule as defineRule21 } from "@oxlint/plugins";
|
|
2379
|
+
function moduleMutableStateBindings(pattern) {
|
|
2380
|
+
if (pattern.type === "Identifier") {
|
|
2381
|
+
return [{ name: pattern.name, node: pattern }];
|
|
2382
|
+
}
|
|
2383
|
+
if (pattern.type === "AssignmentPattern") {
|
|
2384
|
+
return moduleMutableStateBindings(pattern.left);
|
|
2385
|
+
}
|
|
2386
|
+
if (pattern.type === "RestElement") {
|
|
2387
|
+
return moduleMutableStateBindings(pattern.argument);
|
|
2388
|
+
}
|
|
2389
|
+
if (pattern.type === "ObjectPattern") {
|
|
2390
|
+
return pattern.properties.flatMap((property) => property.type === "RestElement" ? moduleMutableStateBindings(property) : moduleMutableStateBindings(property.value));
|
|
2391
|
+
}
|
|
2392
|
+
if (pattern.type === "ArrayPattern") {
|
|
2393
|
+
return pattern.elements.flatMap((element) => element ? moduleMutableStateBindings(element) : []);
|
|
2394
|
+
}
|
|
2395
|
+
return [];
|
|
2396
|
+
}
|
|
2397
|
+
function moduleMutableStateInsideFunction(node) {
|
|
2398
|
+
for (let current = node.parent;current; current = current.parent) {
|
|
2399
|
+
if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression" || current.type === "TSDeclareFunction" || current.type === "TSEmptyBodyFunctionExpression" || current.type === "StaticBlock") {
|
|
2400
|
+
return true;
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
return false;
|
|
2404
|
+
}
|
|
2405
|
+
function moduleMutableStateIsModuleLet(node) {
|
|
2406
|
+
const parent = node.parent;
|
|
2407
|
+
return parent?.type === "Program" || parent?.type === "ExportNamedDeclaration" && parent.parent?.type === "Program";
|
|
2408
|
+
}
|
|
2409
|
+
function moduleMutableStateInsideAmbient(node) {
|
|
2410
|
+
for (let current = node.parent;current; current = current.parent) {
|
|
2411
|
+
if (current.type === "TSModuleDeclaration" && (current.declare || current.global || current.id.type === "Literal")) {
|
|
2412
|
+
return true;
|
|
2413
|
+
}
|
|
2414
|
+
}
|
|
2415
|
+
return false;
|
|
2416
|
+
}
|
|
2417
|
+
var noModuleMutableState = defineRule21({
|
|
2418
|
+
meta: {
|
|
2419
|
+
type: "problem",
|
|
2420
|
+
schema: [{
|
|
2421
|
+
type: "object",
|
|
2422
|
+
additionalProperties: false,
|
|
2423
|
+
properties: {
|
|
2424
|
+
kinds: {
|
|
2425
|
+
type: "array",
|
|
2426
|
+
items: { type: "string", enum: ["let", "var"] }
|
|
2427
|
+
},
|
|
2428
|
+
allowNamePattern: { type: "string" }
|
|
2429
|
+
}
|
|
2430
|
+
}],
|
|
2431
|
+
messages: {
|
|
2432
|
+
moduleState: "module state is shared across requests/tests; keep it local."
|
|
2433
|
+
}
|
|
2434
|
+
},
|
|
2435
|
+
createOnce(context) {
|
|
2436
|
+
let kindSet = new Set(["let", "var"]);
|
|
2437
|
+
let allowed = null;
|
|
2438
|
+
return {
|
|
2439
|
+
before() {
|
|
2440
|
+
const { kinds = ["let", "var"], allowNamePattern } = optionsFirst(context, {});
|
|
2441
|
+
kindSet = new Set(kinds);
|
|
2442
|
+
allowed = allowNamePattern ? new RegExp(allowNamePattern) : null;
|
|
2443
|
+
},
|
|
2444
|
+
VariableDeclaration(node) {
|
|
2445
|
+
if (node.declare || !kindSet.has(node.kind)) {
|
|
2446
|
+
return;
|
|
2447
|
+
}
|
|
2448
|
+
if (node.kind === "let" && !moduleMutableStateIsModuleLet(node) || node.kind === "var" && moduleMutableStateInsideFunction(node) || moduleMutableStateInsideAmbient(node)) {
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
for (const declarator of node.declarations) {
|
|
2452
|
+
for (const binding of moduleMutableStateBindings(declarator.id)) {
|
|
2453
|
+
if (!allowed?.test(binding.name)) {
|
|
2454
|
+
context.report({
|
|
2455
|
+
node: binding.node,
|
|
2456
|
+
messageId: "moduleState"
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
});
|
|
2465
|
+
|
|
2466
|
+
// src/rules/no-promise-all-mutation.ts
|
|
2467
|
+
import { defineRule as defineRule22 } from "@oxlint/plugins";
|
|
2468
|
+
function mutationBinding(node) {
|
|
2469
|
+
if (node.type === "Identifier") {
|
|
2470
|
+
return node;
|
|
2471
|
+
}
|
|
2472
|
+
if (node.type === "MemberExpression") {
|
|
2473
|
+
return mutationBinding(node.object);
|
|
2474
|
+
}
|
|
2475
|
+
if (node.type === "ChainExpression" || node.type === "ParenthesizedExpression" || node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression") {
|
|
2476
|
+
return mutationBinding(node.expression);
|
|
2477
|
+
}
|
|
2478
|
+
}
|
|
2479
|
+
function mutationBindings(node) {
|
|
2480
|
+
switch (node.type) {
|
|
2481
|
+
case "ArrayPattern":
|
|
2482
|
+
return node.elements.flatMap((element) => element ? mutationBindings(element) : []);
|
|
2483
|
+
case "ObjectPattern":
|
|
2484
|
+
return node.properties.flatMap((property) => property.type === "RestElement" ? mutationBindings(property) : mutationBindings(property.value));
|
|
2485
|
+
case "AssignmentPattern":
|
|
2486
|
+
return mutationBindings(node.left);
|
|
2487
|
+
case "RestElement":
|
|
2488
|
+
return mutationBindings(node.argument);
|
|
2489
|
+
default: {
|
|
2490
|
+
const binding = mutationBinding(node);
|
|
2491
|
+
return binding ? [binding] : [];
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
var noPromiseAllMutation = defineRule22({
|
|
2496
|
+
meta: {
|
|
2497
|
+
type: "problem",
|
|
2498
|
+
schema: [{
|
|
2499
|
+
type: "object",
|
|
2500
|
+
additionalProperties: false,
|
|
2501
|
+
properties: {
|
|
2502
|
+
combinators: { type: "array", items: { type: "string" } },
|
|
2503
|
+
methods: { type: "array", items: { type: "string" } },
|
|
2504
|
+
checkAssignments: { type: "boolean" }
|
|
2505
|
+
}
|
|
2506
|
+
}],
|
|
2507
|
+
defaultOptions: [{
|
|
2508
|
+
combinators: ["Promise.all", "Promise.allSettled", "Promise.any", "Promise.race"],
|
|
2509
|
+
methods: ["push", "unshift", "splice", "set", "add"],
|
|
2510
|
+
checkAssignments: true
|
|
2511
|
+
}],
|
|
2512
|
+
messages: {
|
|
2513
|
+
mutation: "Do not mutate outer binding '{{name}}' inside {{combinator}}()."
|
|
2514
|
+
}
|
|
2515
|
+
},
|
|
2516
|
+
createOnce(context) {
|
|
2517
|
+
const references = new Map;
|
|
2518
|
+
const reported = new Set;
|
|
2519
|
+
let combinators = new Set;
|
|
2520
|
+
let methods = new Set;
|
|
2521
|
+
let checkAssignments = true;
|
|
2522
|
+
let referencesReady = false;
|
|
2523
|
+
function ensureReferences() {
|
|
2524
|
+
if (referencesReady) {
|
|
2525
|
+
return;
|
|
2526
|
+
}
|
|
2527
|
+
referencesReady = true;
|
|
2528
|
+
for (const scope of context.sourceCode.scopeManager.scopes) {
|
|
2529
|
+
for (const reference of scope.references) {
|
|
2530
|
+
references.set(reference.identifier.range[0], reference.resolved);
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
function isOuterBinding(identifier, call) {
|
|
2535
|
+
const variable = references.get(identifier.range[0]);
|
|
2536
|
+
return variable !== undefined && variable !== null && variable.identifiers.length > 0 && variable.identifiers.every((declaration) => declaration.range[0] < call.range[0] || declaration.range[1] > call.range[1]);
|
|
2537
|
+
}
|
|
2538
|
+
function reportMutation(node, binding, call, combinator) {
|
|
2539
|
+
if (!binding || reported.has(binding.range[0]) || !isOuterBinding(binding, call)) {
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
reported.add(binding.range[0]);
|
|
2543
|
+
context.report({
|
|
2544
|
+
node,
|
|
2545
|
+
messageId: "mutation",
|
|
2546
|
+
data: { name: binding.name, combinator }
|
|
2547
|
+
});
|
|
2548
|
+
}
|
|
2549
|
+
return {
|
|
2550
|
+
before() {
|
|
2551
|
+
const options = optionsFirst(context);
|
|
2552
|
+
combinators = new Set(options.combinators);
|
|
2553
|
+
methods = new Set(options.methods);
|
|
2554
|
+
checkAssignments = options.checkAssignments;
|
|
2555
|
+
references.clear();
|
|
2556
|
+
reported.clear();
|
|
2557
|
+
referencesReady = false;
|
|
1367
2558
|
},
|
|
1368
2559
|
CallExpression(node) {
|
|
1369
|
-
|
|
2560
|
+
const combinator = astDottedName(node.callee);
|
|
2561
|
+
if (!combinator || !combinators.has(combinator)) {
|
|
1370
2562
|
return;
|
|
1371
2563
|
}
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
node,
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
2564
|
+
ensureReferences();
|
|
2565
|
+
for (const argument of node.arguments) {
|
|
2566
|
+
astVisit(argument, [node], (candidate) => {
|
|
2567
|
+
if (candidate.type === "CallExpression" && candidate.callee.type === "MemberExpression") {
|
|
2568
|
+
const method = astStaticMemberName(candidate.callee);
|
|
2569
|
+
if (method && methods.has(method)) {
|
|
2570
|
+
reportMutation(candidate, mutationBinding(candidate.callee.object), node, combinator);
|
|
2571
|
+
}
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
if (checkAssignments && candidate.type === "AssignmentExpression") {
|
|
2575
|
+
for (const binding of mutationBindings(candidate.left)) {
|
|
2576
|
+
reportMutation(candidate, binding, node, combinator);
|
|
2577
|
+
}
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
if (checkAssignments && candidate.type === "UpdateExpression") {
|
|
2581
|
+
reportMutation(candidate, mutationBinding(candidate.argument), node, combinator);
|
|
2582
|
+
}
|
|
2583
|
+
});
|
|
2584
|
+
}
|
|
1380
2585
|
}
|
|
1381
2586
|
};
|
|
1382
2587
|
}
|
|
1383
2588
|
});
|
|
1384
2589
|
|
|
1385
2590
|
// src/rules/no-rederive-schema.ts
|
|
1386
|
-
import { defineRule as
|
|
1387
|
-
var noRederiveSchema =
|
|
2591
|
+
import { defineRule as defineRule23 } from "@oxlint/plugins";
|
|
2592
|
+
var noRederiveSchema = defineRule23({
|
|
1388
2593
|
meta: {
|
|
1389
2594
|
type: "problem",
|
|
1390
2595
|
schema: [{
|
|
@@ -1428,9 +2633,98 @@ var noRederiveSchema = defineRule13({
|
|
|
1428
2633
|
}
|
|
1429
2634
|
});
|
|
1430
2635
|
|
|
2636
|
+
// src/rules/no-restricted-constructor.ts
|
|
2637
|
+
import { defineRule as defineRule24 } from "@oxlint/plugins";
|
|
2638
|
+
function restrictedConstructorEntries(constructors) {
|
|
2639
|
+
const entries = new Map;
|
|
2640
|
+
for (const item of constructors) {
|
|
2641
|
+
if (typeof item === "string") {
|
|
2642
|
+
if (!entries.has(item)) {
|
|
2643
|
+
entries.set(item, undefined);
|
|
2644
|
+
}
|
|
2645
|
+
continue;
|
|
2646
|
+
}
|
|
2647
|
+
if (!entries.has(item.name)) {
|
|
2648
|
+
entries.set(item.name, item.message);
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
return entries;
|
|
2652
|
+
}
|
|
2653
|
+
var noRestrictedConstructor = defineRule24({
|
|
2654
|
+
meta: {
|
|
2655
|
+
type: "problem",
|
|
2656
|
+
schema: [{
|
|
2657
|
+
type: "object",
|
|
2658
|
+
additionalProperties: false,
|
|
2659
|
+
properties: {
|
|
2660
|
+
constructors: {
|
|
2661
|
+
type: "array",
|
|
2662
|
+
items: {
|
|
2663
|
+
anyOf: [
|
|
2664
|
+
{ type: "string" },
|
|
2665
|
+
{
|
|
2666
|
+
type: "object",
|
|
2667
|
+
additionalProperties: false,
|
|
2668
|
+
properties: {
|
|
2669
|
+
name: { type: "string" },
|
|
2670
|
+
message: { type: "string" }
|
|
2671
|
+
},
|
|
2672
|
+
required: ["name"]
|
|
2673
|
+
}
|
|
2674
|
+
]
|
|
2675
|
+
}
|
|
2676
|
+
},
|
|
2677
|
+
checkCalls: { type: "boolean", default: false },
|
|
2678
|
+
message: { type: "string" }
|
|
2679
|
+
},
|
|
2680
|
+
required: ["constructors"]
|
|
2681
|
+
}],
|
|
2682
|
+
messages: {
|
|
2683
|
+
construct: "Do not construct '{{name}}'.{{suffix}}",
|
|
2684
|
+
call: "Do not call '{{name}}'.{{suffix}}"
|
|
2685
|
+
}
|
|
2686
|
+
},
|
|
2687
|
+
createOnce(context) {
|
|
2688
|
+
let entries = new Map;
|
|
2689
|
+
let checkCalls = false;
|
|
2690
|
+
let fallbackMessage;
|
|
2691
|
+
function report(node, messageId) {
|
|
2692
|
+
const name = astDottedName(node.callee);
|
|
2693
|
+
if (!name || !entries.has(name)) {
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
const extra = entries.get(name) ?? fallbackMessage;
|
|
2697
|
+
context.report({
|
|
2698
|
+
node,
|
|
2699
|
+
messageId,
|
|
2700
|
+
data: { name, suffix: extra ? ` ${extra}` : "" }
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
2703
|
+
return {
|
|
2704
|
+
before() {
|
|
2705
|
+
const options = optionsFirst(context);
|
|
2706
|
+
entries = restrictedConstructorEntries(options.constructors);
|
|
2707
|
+
checkCalls = options.checkCalls === true;
|
|
2708
|
+
fallbackMessage = options.message;
|
|
2709
|
+
if (entries.size === 0) {
|
|
2710
|
+
return false;
|
|
2711
|
+
}
|
|
2712
|
+
},
|
|
2713
|
+
NewExpression(node) {
|
|
2714
|
+
report(node, "construct");
|
|
2715
|
+
},
|
|
2716
|
+
CallExpression(node) {
|
|
2717
|
+
if (checkCalls) {
|
|
2718
|
+
report(node, "call");
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2723
|
+
});
|
|
2724
|
+
|
|
1431
2725
|
// src/rules/no-restricted-files.ts
|
|
1432
|
-
import { defineRule as
|
|
1433
|
-
var noRestrictedFiles =
|
|
2726
|
+
import { defineRule as defineRule25 } from "@oxlint/plugins";
|
|
2727
|
+
var noRestrictedFiles = defineRule25({
|
|
1434
2728
|
meta: {
|
|
1435
2729
|
type: "problem",
|
|
1436
2730
|
schema: [{
|
|
@@ -1460,7 +2754,7 @@ var noRestrictedFiles = defineRule14({
|
|
|
1460
2754
|
});
|
|
1461
2755
|
|
|
1462
2756
|
// src/rules/no-restricted-token.ts
|
|
1463
|
-
import { defineRule as
|
|
2757
|
+
import { defineRule as defineRule26 } from "@oxlint/plugins";
|
|
1464
2758
|
function tokenIsTypeOnlyImport(node) {
|
|
1465
2759
|
const parent = node.parent;
|
|
1466
2760
|
if (parent?.type === "ImportSpecifier") {
|
|
@@ -1468,7 +2762,10 @@ function tokenIsTypeOnlyImport(node) {
|
|
|
1468
2762
|
}
|
|
1469
2763
|
return (parent?.type === "ImportDefaultSpecifier" || parent?.type === "ImportNamespaceSpecifier") && parent.parent.type === "ImportDeclaration" && parent.parent.importKind === "type";
|
|
1470
2764
|
}
|
|
1471
|
-
|
|
2765
|
+
function memberMatches(node, restriction) {
|
|
2766
|
+
return restriction.startsWith("*.") ? astStaticMemberName(node) === restriction.slice(2) : astDottedName(node) === restriction;
|
|
2767
|
+
}
|
|
2768
|
+
var noRestrictedToken = defineRule26({
|
|
1472
2769
|
meta: {
|
|
1473
2770
|
type: "problem",
|
|
1474
2771
|
schema: [{
|
|
@@ -1477,32 +2774,86 @@ var noRestrictedToken = defineRule15({
|
|
|
1477
2774
|
properties: {
|
|
1478
2775
|
token: { type: "string" },
|
|
1479
2776
|
allowIn: { type: "array", items: { type: "string" } },
|
|
1480
|
-
allowPathPatterns: { type: "array", items: { type: "string" } }
|
|
2777
|
+
allowPathPatterns: { type: "array", items: { type: "string" } },
|
|
2778
|
+
restrictions: {
|
|
2779
|
+
type: "array",
|
|
2780
|
+
items: {
|
|
2781
|
+
type: "object",
|
|
2782
|
+
additionalProperties: false,
|
|
2783
|
+
properties: {
|
|
2784
|
+
token: { type: "string" },
|
|
2785
|
+
member: { type: "string" },
|
|
2786
|
+
allowIn: { type: "array", items: { type: "string" } },
|
|
2787
|
+
allowPathPatterns: { type: "array", items: { type: "string" } },
|
|
2788
|
+
message: { type: "string" }
|
|
2789
|
+
},
|
|
2790
|
+
oneOf: [
|
|
2791
|
+
{ required: ["token"], not: { required: ["member"] } },
|
|
2792
|
+
{ required: ["member"], not: { required: ["token"] } }
|
|
2793
|
+
]
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
1481
2796
|
},
|
|
1482
|
-
|
|
2797
|
+
anyOf: [
|
|
2798
|
+
{ required: ["token"] },
|
|
2799
|
+
{ required: ["restrictions"] }
|
|
2800
|
+
]
|
|
1483
2801
|
}],
|
|
1484
2802
|
messages: {
|
|
1485
|
-
restricted: "'{{token}}' may only appear in configured owner paths."
|
|
2803
|
+
restricted: "'{{token}}' may only appear in configured owner paths.",
|
|
2804
|
+
restrictedWithMessage: "'{{token}}' may only appear in configured owner paths. {{custom}}"
|
|
1486
2805
|
}
|
|
1487
2806
|
},
|
|
1488
2807
|
createOnce(context) {
|
|
2808
|
+
let restrictions = [];
|
|
2809
|
+
function report(node, restriction) {
|
|
2810
|
+
context.report({
|
|
2811
|
+
node,
|
|
2812
|
+
messageId: restriction.message === undefined ? "restricted" : "restrictedWithMessage",
|
|
2813
|
+
data: { token: restriction.value, custom: restriction.message ?? "" }
|
|
2814
|
+
});
|
|
2815
|
+
}
|
|
1489
2816
|
return {
|
|
1490
2817
|
before() {
|
|
1491
|
-
const
|
|
2818
|
+
const options = optionsFirst(context);
|
|
2819
|
+
const configured = [
|
|
2820
|
+
...options.token ? [{
|
|
2821
|
+
token: options.token,
|
|
2822
|
+
allowIn: options.allowIn,
|
|
2823
|
+
allowPathPatterns: options.allowPathPatterns
|
|
2824
|
+
}] : [],
|
|
2825
|
+
...options.restrictions ?? []
|
|
2826
|
+
];
|
|
1492
2827
|
const filename = namingPosixPath(context.filename);
|
|
1493
|
-
|
|
1494
|
-
|
|
2828
|
+
restrictions = configured.flatMap((restriction) => {
|
|
2829
|
+
const allowed = (restriction.allowIn ?? []).some((suffix) => filename.endsWith(suffix)) || (restriction.allowPathPatterns ?? []).some((pattern) => new RegExp(pattern).test(filename));
|
|
2830
|
+
if (allowed) {
|
|
2831
|
+
return [];
|
|
2832
|
+
}
|
|
2833
|
+
if (restriction.token) {
|
|
2834
|
+
return [{ kind: "token", value: restriction.token, message: restriction.message }];
|
|
2835
|
+
}
|
|
2836
|
+
return restriction.member ? [{ kind: "member", value: restriction.member, message: restriction.message }] : [];
|
|
2837
|
+
});
|
|
2838
|
+
if (restrictions.length === 0) {
|
|
1495
2839
|
return false;
|
|
1496
2840
|
}
|
|
1497
2841
|
},
|
|
1498
2842
|
Identifier(node) {
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
}
|
|
2843
|
+
if (tokenIsTypeOnlyImport(node)) {
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2846
|
+
for (const restriction of restrictions) {
|
|
2847
|
+
if (restriction.kind === "token" && node.name === restriction.value) {
|
|
2848
|
+
report(node, restriction);
|
|
2849
|
+
}
|
|
2850
|
+
}
|
|
2851
|
+
},
|
|
2852
|
+
MemberExpression(node) {
|
|
2853
|
+
for (const restriction of restrictions) {
|
|
2854
|
+
if (restriction.kind === "member" && memberMatches(node, restriction.value)) {
|
|
2855
|
+
report(node, restriction);
|
|
2856
|
+
}
|
|
1506
2857
|
}
|
|
1507
2858
|
}
|
|
1508
2859
|
};
|
|
@@ -1510,8 +2861,224 @@ var noRestrictedToken = defineRule15({
|
|
|
1510
2861
|
});
|
|
1511
2862
|
|
|
1512
2863
|
// src/rules/no-runtime-in-types.ts
|
|
1513
|
-
import { defineRule as
|
|
1514
|
-
|
|
2864
|
+
import { defineRule as defineRule27 } from "@oxlint/plugins";
|
|
2865
|
+
function isTypeOnlyImport(statement) {
|
|
2866
|
+
return statement.importKind === "type" || statement.specifiers.length > 0 && statement.specifiers.every((specifier) => specifier.type === "ImportSpecifier" && specifier.importKind === "type");
|
|
2867
|
+
}
|
|
2868
|
+
function exportLocalName(specifier) {
|
|
2869
|
+
return specifier.local.type === "Identifier" ? specifier.local.name : specifier.local.value;
|
|
2870
|
+
}
|
|
2871
|
+
function importEqualsSource(declaration) {
|
|
2872
|
+
return declaration.moduleReference.type === "TSExternalModuleReference" ? declaration.moduleReference.expression.value : undefined;
|
|
2873
|
+
}
|
|
2874
|
+
function importEqualsDeclaration(statement) {
|
|
2875
|
+
if (statement.type === "TSImportEqualsDeclaration") {
|
|
2876
|
+
return statement;
|
|
2877
|
+
}
|
|
2878
|
+
return statement.type === "ExportNamedDeclaration" && statement.declaration?.type === "TSImportEqualsDeclaration" ? statement.declaration : undefined;
|
|
2879
|
+
}
|
|
2880
|
+
function isAllowedSource(source, allowedImportSources) {
|
|
2881
|
+
return allowedImportSources.some((pattern) => {
|
|
2882
|
+
pattern.lastIndex = 0;
|
|
2883
|
+
return pattern.test(source);
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
function runtimeInTypesIsAmbientFile(filename) {
|
|
2887
|
+
return filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts");
|
|
2888
|
+
}
|
|
2889
|
+
function runtimeInTypesUnwrapped(statement) {
|
|
2890
|
+
return statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration ?? undefined : statement;
|
|
2891
|
+
}
|
|
2892
|
+
function runtimeInTypesEntityRootName(node) {
|
|
2893
|
+
let current = node;
|
|
2894
|
+
while (current.type === "TSQualifiedName") {
|
|
2895
|
+
current = current.left;
|
|
2896
|
+
}
|
|
2897
|
+
return current.type === "Identifier" ? current.name : undefined;
|
|
2898
|
+
}
|
|
2899
|
+
function importEqualsRootName(declaration) {
|
|
2900
|
+
return declaration.moduleReference.type === "TSExternalModuleReference" ? undefined : runtimeInTypesEntityRootName(declaration.moduleReference);
|
|
2901
|
+
}
|
|
2902
|
+
function runtimeInTypesModuleBindingName(node) {
|
|
2903
|
+
return node.type === "TSModuleDeclaration" ? runtimeInTypesEntityRootName(node.id) : undefined;
|
|
2904
|
+
}
|
|
2905
|
+
function runtimeInTypesIsInternalValueAlias(declaration, valueNames) {
|
|
2906
|
+
if (declaration.importKind === "type" || importEqualsSource(declaration) !== undefined) {
|
|
2907
|
+
return false;
|
|
2908
|
+
}
|
|
2909
|
+
const root = importEqualsRootName(declaration);
|
|
2910
|
+
return root !== undefined && valueNames.has(root);
|
|
2911
|
+
}
|
|
2912
|
+
function runtimeInTypesModuleName(node) {
|
|
2913
|
+
if (node.type !== "TSModuleDeclaration") {
|
|
2914
|
+
return "namespace";
|
|
2915
|
+
}
|
|
2916
|
+
return node.id.type === "Identifier" ? node.id.name : node.id.type === "Literal" ? String(node.id.value) : "namespace";
|
|
2917
|
+
}
|
|
2918
|
+
function runtimeInTypesNamespaceIsRuntime(node, isAmbientFile, valueNames) {
|
|
2919
|
+
if (isAmbientFile || node.type !== "TSModuleDeclaration" || node.declare || node.global || node.id.type === "Literal" || !node.body) {
|
|
2920
|
+
return false;
|
|
2921
|
+
}
|
|
2922
|
+
return node.body.body.some((statement) => runtimeInTypesNamespaceMemberIsRuntime(statement, isAmbientFile, valueNames));
|
|
2923
|
+
}
|
|
2924
|
+
function runtimeInTypesNamespaceMemberIsRuntime(node, isAmbientFile, valueNames) {
|
|
2925
|
+
if (node.type === "ExportNamedDeclaration") {
|
|
2926
|
+
if (node.exportKind === "type") {
|
|
2927
|
+
return false;
|
|
2928
|
+
}
|
|
2929
|
+
return node.declaration ? runtimeInTypesNamespaceMemberIsRuntime(node.declaration, isAmbientFile, valueNames) : node.specifiers.some((specifier) => specifier.exportKind !== "type");
|
|
2930
|
+
}
|
|
2931
|
+
if (node.type === "TSModuleDeclaration") {
|
|
2932
|
+
return runtimeInTypesNamespaceIsRuntime(node, isAmbientFile, valueNames);
|
|
2933
|
+
}
|
|
2934
|
+
if (node.type === "TSImportEqualsDeclaration") {
|
|
2935
|
+
return node.importKind !== "type" && (node.moduleReference.type === "TSExternalModuleReference" || runtimeInTypesIsInternalValueAlias(node, valueNames));
|
|
2936
|
+
}
|
|
2937
|
+
if (node.type === "TSExportAssignment") {
|
|
2938
|
+
return true;
|
|
2939
|
+
}
|
|
2940
|
+
return declarationsIsRuntime(node) && !node.declare;
|
|
2941
|
+
}
|
|
2942
|
+
function runtimeInTypesMemberHasValue(node) {
|
|
2943
|
+
if (node.type === "ExportNamedDeclaration") {
|
|
2944
|
+
if (node.exportKind === "type") {
|
|
2945
|
+
return false;
|
|
2946
|
+
}
|
|
2947
|
+
return node.declaration ? runtimeInTypesMemberHasValue(node.declaration) : node.specifiers.some((specifier) => specifier.exportKind !== "type");
|
|
2948
|
+
}
|
|
2949
|
+
if (node.type === "TSModuleDeclaration") {
|
|
2950
|
+
return runtimeInTypesNamespaceHasValueMembers(node);
|
|
2951
|
+
}
|
|
2952
|
+
if (node.type === "TSImportEqualsDeclaration") {
|
|
2953
|
+
return node.importKind !== "type" && node.moduleReference.type === "TSExternalModuleReference";
|
|
2954
|
+
}
|
|
2955
|
+
if (node.type === "TSExportAssignment") {
|
|
2956
|
+
return true;
|
|
2957
|
+
}
|
|
2958
|
+
return declarationsIsRuntime(node);
|
|
2959
|
+
}
|
|
2960
|
+
function runtimeInTypesNamespaceHasValueMembers(node) {
|
|
2961
|
+
if (node.type !== "TSModuleDeclaration" || node.global || node.id.type === "Literal" || !node.body) {
|
|
2962
|
+
return false;
|
|
2963
|
+
}
|
|
2964
|
+
return node.body.body.some((statement) => runtimeInTypesMemberHasValue(statement));
|
|
2965
|
+
}
|
|
2966
|
+
function runtimeInTypesMemberReferencesValue(node, valueNames) {
|
|
2967
|
+
if (node.type === "ExportNamedDeclaration") {
|
|
2968
|
+
return node.declaration ? runtimeInTypesMemberReferencesValue(node.declaration, valueNames) : false;
|
|
2969
|
+
}
|
|
2970
|
+
if (node.type === "TSModuleDeclaration") {
|
|
2971
|
+
return runtimeInTypesNamespaceHasValueMembers(node) || runtimeInTypesNamespaceReferencesValue(node, valueNames);
|
|
2972
|
+
}
|
|
2973
|
+
if (node.type === "TSImportEqualsDeclaration") {
|
|
2974
|
+
return runtimeInTypesIsInternalValueAlias(node, valueNames);
|
|
2975
|
+
}
|
|
2976
|
+
return false;
|
|
2977
|
+
}
|
|
2978
|
+
function runtimeInTypesNamespaceReferencesValue(node, valueNames) {
|
|
2979
|
+
if (node.type !== "TSModuleDeclaration" || !node.body) {
|
|
2980
|
+
return false;
|
|
2981
|
+
}
|
|
2982
|
+
return node.body.body.some((statement) => runtimeInTypesMemberReferencesValue(statement, valueNames));
|
|
2983
|
+
}
|
|
2984
|
+
function runtimeInTypesSeedValueName(declaration, valueNames) {
|
|
2985
|
+
if (declaration.type === "VariableDeclaration") {
|
|
2986
|
+
for (const item of declaration.declarations) {
|
|
2987
|
+
if (item.id.type === "Identifier") {
|
|
2988
|
+
valueNames.add(item.id.name);
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
return;
|
|
2992
|
+
}
|
|
2993
|
+
if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration" || declaration.type === "TSEnumDeclaration") && declaration.id) {
|
|
2994
|
+
valueNames.add(declaration.id.name);
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
if (declaration.type === "TSModuleDeclaration") {
|
|
2998
|
+
const name = runtimeInTypesModuleBindingName(declaration);
|
|
2999
|
+
if (name && runtimeInTypesNamespaceHasValueMembers(declaration)) {
|
|
3000
|
+
valueNames.add(name);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
function runtimeInTypesValueNames(statements) {
|
|
3005
|
+
const valueNames = new Set;
|
|
3006
|
+
for (const statement of statements) {
|
|
3007
|
+
if (statement.type === "ImportDeclaration") {
|
|
3008
|
+
if (isTypeOnlyImport(statement)) {
|
|
3009
|
+
continue;
|
|
3010
|
+
}
|
|
3011
|
+
for (const specifier of statement.specifiers) {
|
|
3012
|
+
if (specifier.type !== "ImportSpecifier" || specifier.importKind !== "type") {
|
|
3013
|
+
valueNames.add(specifier.local.name);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
continue;
|
|
3017
|
+
}
|
|
3018
|
+
const importEquals = importEqualsDeclaration(statement);
|
|
3019
|
+
if (importEquals) {
|
|
3020
|
+
if (importEquals.importKind !== "type" && importEqualsSource(importEquals) !== undefined) {
|
|
3021
|
+
valueNames.add(importEquals.id.name);
|
|
3022
|
+
}
|
|
3023
|
+
continue;
|
|
3024
|
+
}
|
|
3025
|
+
const declaration = runtimeInTypesUnwrapped(statement);
|
|
3026
|
+
if (declaration) {
|
|
3027
|
+
runtimeInTypesSeedValueName(declaration, valueNames);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
let changed = true;
|
|
3031
|
+
while (changed) {
|
|
3032
|
+
changed = false;
|
|
3033
|
+
for (const statement of statements) {
|
|
3034
|
+
const importEquals = importEqualsDeclaration(statement);
|
|
3035
|
+
if (importEquals && runtimeInTypesIsInternalValueAlias(importEquals, valueNames)) {
|
|
3036
|
+
if (!valueNames.has(importEquals.id.name)) {
|
|
3037
|
+
valueNames.add(importEquals.id.name);
|
|
3038
|
+
changed = true;
|
|
3039
|
+
}
|
|
3040
|
+
continue;
|
|
3041
|
+
}
|
|
3042
|
+
const declaration = runtimeInTypesUnwrapped(statement);
|
|
3043
|
+
if (declaration?.type !== "TSModuleDeclaration") {
|
|
3044
|
+
continue;
|
|
3045
|
+
}
|
|
3046
|
+
const name = runtimeInTypesModuleBindingName(declaration);
|
|
3047
|
+
if (name && !valueNames.has(name) && runtimeInTypesNamespaceReferencesValue(declaration, valueNames)) {
|
|
3048
|
+
valueNames.add(name);
|
|
3049
|
+
changed = true;
|
|
3050
|
+
}
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
return valueNames;
|
|
3054
|
+
}
|
|
3055
|
+
function runtimeInTypesExportAssignmentExpression(node) {
|
|
3056
|
+
let expression = node.expression;
|
|
3057
|
+
while (expression.type === "ParenthesizedExpression") {
|
|
3058
|
+
expression = expression.expression;
|
|
3059
|
+
}
|
|
3060
|
+
return expression;
|
|
3061
|
+
}
|
|
3062
|
+
function isRuntimeReExport(statement, runtimeImportLocals, valueNames, isAmbientFile) {
|
|
3063
|
+
if (statement.exportKind === "type") {
|
|
3064
|
+
return false;
|
|
3065
|
+
}
|
|
3066
|
+
if (statement.type === "ExportAllDeclaration") {
|
|
3067
|
+
return true;
|
|
3068
|
+
}
|
|
3069
|
+
if (statement.declaration?.type === "TSImportEqualsDeclaration") {
|
|
3070
|
+
const declaration = statement.declaration;
|
|
3071
|
+
if (declaration.importKind === "type" || isAmbientFile) {
|
|
3072
|
+
return false;
|
|
3073
|
+
}
|
|
3074
|
+
return importEqualsSource(declaration) !== undefined || runtimeInTypesIsInternalValueAlias(declaration, valueNames);
|
|
3075
|
+
}
|
|
3076
|
+
if (statement.source) {
|
|
3077
|
+
return statement.specifiers.length === 0 || statement.specifiers.some((specifier) => specifier.exportKind !== "type");
|
|
3078
|
+
}
|
|
3079
|
+
return statement.specifiers.some((specifier) => specifier.exportKind !== "type" && runtimeImportLocals.has(exportLocalName(specifier)));
|
|
3080
|
+
}
|
|
3081
|
+
var noRuntimeInTypes = defineRule27({
|
|
1515
3082
|
meta: {
|
|
1516
3083
|
type: "problem",
|
|
1517
3084
|
schema: [{
|
|
@@ -1521,18 +3088,80 @@ var noRuntimeInTypes = defineRule16({
|
|
|
1521
3088
|
allow: {
|
|
1522
3089
|
type: "array",
|
|
1523
3090
|
items: { type: "string", enum: ["class", "default", "enum", "function", "variable"] }
|
|
1524
|
-
}
|
|
3091
|
+
},
|
|
3092
|
+
runtimeImports: { type: "string", enum: ["allow", "ban"] },
|
|
3093
|
+
allowImportSources: { type: "array", items: { type: "string" } },
|
|
3094
|
+
banReExports: { type: "boolean" }
|
|
1525
3095
|
}
|
|
1526
3096
|
}],
|
|
1527
3097
|
messages: {
|
|
1528
|
-
runtimeValue: "Types files must not contain runtime value {{name}}."
|
|
3098
|
+
runtimeValue: "Types files must not contain runtime value {{name}}.",
|
|
3099
|
+
runtimeImport: "Types files must not contain runtime imports from {{source}}.",
|
|
3100
|
+
runtimeReExport: "Types files must not contain runtime re-exports."
|
|
1529
3101
|
}
|
|
1530
3102
|
},
|
|
1531
3103
|
createOnce(context) {
|
|
3104
|
+
let allow = [];
|
|
3105
|
+
let runtimeImports = "allow";
|
|
3106
|
+
let allowedImportSources = [];
|
|
3107
|
+
let banReExports = false;
|
|
3108
|
+
let isAmbientFile = false;
|
|
1532
3109
|
return {
|
|
3110
|
+
before() {
|
|
3111
|
+
const options = optionsFirst(context, {});
|
|
3112
|
+
allow = options.allow ?? [];
|
|
3113
|
+
runtimeImports = options.runtimeImports ?? "allow";
|
|
3114
|
+
allowedImportSources = (options.allowImportSources ?? []).map((pattern) => new RegExp(pattern));
|
|
3115
|
+
banReExports = options.banReExports === true;
|
|
3116
|
+
isAmbientFile = runtimeInTypesIsAmbientFile(context.filename);
|
|
3117
|
+
},
|
|
1533
3118
|
Program(program) {
|
|
1534
|
-
const
|
|
3119
|
+
const runtimeImportLocals = new Set;
|
|
3120
|
+
const valueNames = runtimeInTypesValueNames(program.body);
|
|
3121
|
+
for (const statement of program.body) {
|
|
3122
|
+
if (statement.type === "ImportDeclaration") {
|
|
3123
|
+
if (isTypeOnlyImport(statement)) {
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
3126
|
+
for (const specifier of statement.specifiers) {
|
|
3127
|
+
if (specifier.type !== "ImportSpecifier" || specifier.importKind !== "type") {
|
|
3128
|
+
runtimeImportLocals.add(specifier.local.name);
|
|
3129
|
+
}
|
|
3130
|
+
}
|
|
3131
|
+
const source = String(statement.source.value);
|
|
3132
|
+
if (runtimeImports === "ban" && !isAllowedSource(source, allowedImportSources)) {
|
|
3133
|
+
context.report({
|
|
3134
|
+
node: statement,
|
|
3135
|
+
messageId: "runtimeImport",
|
|
3136
|
+
data: { source }
|
|
3137
|
+
});
|
|
3138
|
+
}
|
|
3139
|
+
continue;
|
|
3140
|
+
}
|
|
3141
|
+
const declaration = importEqualsDeclaration(statement);
|
|
3142
|
+
if (!declaration || declaration.importKind === "type") {
|
|
3143
|
+
continue;
|
|
3144
|
+
}
|
|
3145
|
+
const source = importEqualsSource(declaration);
|
|
3146
|
+
if (source === undefined) {
|
|
3147
|
+
continue;
|
|
3148
|
+
}
|
|
3149
|
+
runtimeImportLocals.add(declaration.id.name);
|
|
3150
|
+
if (runtimeImports === "ban" && !isAllowedSource(source, allowedImportSources)) {
|
|
3151
|
+
context.report({
|
|
3152
|
+
node: declaration,
|
|
3153
|
+
messageId: "runtimeImport",
|
|
3154
|
+
data: { source }
|
|
3155
|
+
});
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
1535
3158
|
for (const statement of program.body) {
|
|
3159
|
+
if (banReExports && (statement.type === "ExportNamedDeclaration" || statement.type === "ExportAllDeclaration") && isRuntimeReExport(statement, runtimeImportLocals, valueNames, isAmbientFile)) {
|
|
3160
|
+
context.report({
|
|
3161
|
+
node: statement,
|
|
3162
|
+
messageId: "runtimeReExport"
|
|
3163
|
+
});
|
|
3164
|
+
}
|
|
1536
3165
|
const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
|
|
1537
3166
|
if (!declaration) {
|
|
1538
3167
|
continue;
|
|
@@ -1547,6 +3176,36 @@ var noRuntimeInTypes = defineRule16({
|
|
|
1547
3176
|
}
|
|
1548
3177
|
continue;
|
|
1549
3178
|
}
|
|
3179
|
+
if (declaration.type === "TSModuleDeclaration") {
|
|
3180
|
+
if (runtimeInTypesNamespaceIsRuntime(declaration, isAmbientFile, valueNames)) {
|
|
3181
|
+
context.report({
|
|
3182
|
+
node: declaration,
|
|
3183
|
+
messageId: "runtimeValue",
|
|
3184
|
+
data: { name: runtimeInTypesModuleName(declaration) }
|
|
3185
|
+
});
|
|
3186
|
+
}
|
|
3187
|
+
continue;
|
|
3188
|
+
}
|
|
3189
|
+
if (statement.type === "TSExportAssignment" && !allow.includes("default")) {
|
|
3190
|
+
const expression = runtimeInTypesExportAssignmentExpression(statement);
|
|
3191
|
+
if (expression.type !== "Identifier" || valueNames.has(expression.name)) {
|
|
3192
|
+
context.report({
|
|
3193
|
+
node: statement,
|
|
3194
|
+
messageId: "runtimeValue",
|
|
3195
|
+
data: { name: expression.type === "Identifier" ? expression.name : "default" }
|
|
3196
|
+
});
|
|
3197
|
+
}
|
|
3198
|
+
continue;
|
|
3199
|
+
}
|
|
3200
|
+
const importEquals = importEqualsDeclaration(statement);
|
|
3201
|
+
if (!isAmbientFile && importEquals && runtimeInTypesIsInternalValueAlias(importEquals, valueNames)) {
|
|
3202
|
+
context.report({
|
|
3203
|
+
node: importEquals,
|
|
3204
|
+
messageId: "runtimeValue",
|
|
3205
|
+
data: { name: importEquals.id.name }
|
|
3206
|
+
});
|
|
3207
|
+
continue;
|
|
3208
|
+
}
|
|
1550
3209
|
if (!allow.includes("default") && statement.type === "ExportDefaultDeclaration" && declaration.type !== "TSInterfaceDeclaration" && declaration.type !== "TSTypeAliasDeclaration") {
|
|
1551
3210
|
context.report({
|
|
1552
3211
|
node: statement,
|
|
@@ -1561,7 +3220,7 @@ var noRuntimeInTypes = defineRule16({
|
|
|
1561
3220
|
});
|
|
1562
3221
|
|
|
1563
3222
|
// src/rules/no-single-use-scalar-schema.ts
|
|
1564
|
-
import { defineRule as
|
|
3223
|
+
import { defineRule as defineRule28 } from "@oxlint/plugins";
|
|
1565
3224
|
function isScalarSchema(node, namespaces, structuralMethods) {
|
|
1566
3225
|
if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
|
|
1567
3226
|
return false;
|
|
@@ -1583,8 +3242,8 @@ function isDirectObjectField(identifier, ancestors, namespaces, structuralMethod
|
|
|
1583
3242
|
const method = call.callee.type === "MemberExpression" ? astStaticMemberName(call.callee) : undefined;
|
|
1584
3243
|
return Boolean(method === "object" && structuralMethods.has(method) && call.arguments.includes(object));
|
|
1585
3244
|
}
|
|
1586
|
-
function isAllowedStructuralElement(identifier, initializer, ancestors,
|
|
1587
|
-
if (!
|
|
3245
|
+
function isAllowedStructuralElement(identifier, initializer, ancestors, options, namespaces, structuralMethods) {
|
|
3246
|
+
if (!options.allowZodScalars || !schemasIsAllowedElement(initializer, true, namespaces, structuralMethods)) {
|
|
1588
3247
|
return false;
|
|
1589
3248
|
}
|
|
1590
3249
|
const parent = ancestors.at(-1);
|
|
@@ -1593,9 +3252,9 @@ function isAllowedStructuralElement(identifier, initializer, ancestors, options2
|
|
|
1593
3252
|
return false;
|
|
1594
3253
|
}
|
|
1595
3254
|
const method = call.callee.type === "MemberExpression" ? astStaticMemberName(call.callee) : undefined;
|
|
1596
|
-
return Boolean(method &&
|
|
3255
|
+
return Boolean(method && options.methods.includes(method) && schemasElements(call).includes(identifier));
|
|
1597
3256
|
}
|
|
1598
|
-
var noSingleUseScalarSchema =
|
|
3257
|
+
var noSingleUseScalarSchema = defineRule28({
|
|
1599
3258
|
meta: {
|
|
1600
3259
|
type: "suggestion",
|
|
1601
3260
|
schema: [{
|
|
@@ -1630,9 +3289,9 @@ var noSingleUseScalarSchema = defineRule17({
|
|
|
1630
3289
|
}
|
|
1631
3290
|
},
|
|
1632
3291
|
"Program:exit"(program) {
|
|
1633
|
-
const
|
|
1634
|
-
const namespaces = new Set(
|
|
1635
|
-
const structuralMethods = new Set(
|
|
3292
|
+
const options = optionsFirst(context);
|
|
3293
|
+
const namespaces = new Set(options.namespaces);
|
|
3294
|
+
const structuralMethods = new Set(options.structuralMethods);
|
|
1636
3295
|
const exportedNames = new Set(exportsCollect(program).flatMap((binding) => binding.localName ? [binding.localName] : []));
|
|
1637
3296
|
for (const declarator of candidates) {
|
|
1638
3297
|
const name = declarator.id.type === "Identifier" ? declarator.id.name : undefined;
|
|
@@ -1641,14 +3300,14 @@ var noSingleUseScalarSchema = defineRule17({
|
|
|
1641
3300
|
continue;
|
|
1642
3301
|
}
|
|
1643
3302
|
const variable = context.sourceCode.getDeclaredVariables(declarator).find((candidate) => candidate.name === name);
|
|
1644
|
-
const references = variable?.references.filter((
|
|
3303
|
+
const references = variable?.references.filter((reference) => reference.isRead()) ?? [];
|
|
1645
3304
|
if (references.length !== 1) {
|
|
1646
3305
|
continue;
|
|
1647
3306
|
}
|
|
1648
3307
|
const reference = references[0];
|
|
1649
3308
|
const identifier = reference.identifier;
|
|
1650
3309
|
const ancestors = context.sourceCode.getAncestors(identifier);
|
|
1651
|
-
const inlineable = isDirectObjectField(identifier, ancestors, namespaces, structuralMethods) || isAllowedStructuralElement(identifier, initializer, ancestors,
|
|
3310
|
+
const inlineable = isDirectObjectField(identifier, ancestors, namespaces, structuralMethods) || isAllowedStructuralElement(identifier, initializer, ancestors, options, namespaces, structuralMethods);
|
|
1652
3311
|
if (inlineable) {
|
|
1653
3312
|
context.report({
|
|
1654
3313
|
node: declarator.id,
|
|
@@ -1663,8 +3322,8 @@ var noSingleUseScalarSchema = defineRule17({
|
|
|
1663
3322
|
});
|
|
1664
3323
|
|
|
1665
3324
|
// src/rules/no-top-level-functions.ts
|
|
1666
|
-
import { defineRule as
|
|
1667
|
-
var noTopLevelFunctions =
|
|
3325
|
+
import { defineRule as defineRule29 } from "@oxlint/plugins";
|
|
3326
|
+
var noTopLevelFunctions = defineRule29({
|
|
1668
3327
|
meta: {
|
|
1669
3328
|
type: "problem",
|
|
1670
3329
|
schema: [{
|
|
@@ -1718,18 +3377,46 @@ var noTopLevelFunctions = defineRule18({
|
|
|
1718
3377
|
}
|
|
1719
3378
|
};
|
|
1720
3379
|
}
|
|
1721
|
-
});
|
|
1722
|
-
|
|
1723
|
-
// src/rules/no-trivial-functions.ts
|
|
1724
|
-
import { defineRule as
|
|
1725
|
-
|
|
3380
|
+
});
|
|
3381
|
+
|
|
3382
|
+
// src/rules/no-trivial-functions.ts
|
|
3383
|
+
import { defineRule as defineRule30 } from "@oxlint/plugins";
|
|
3384
|
+
function forwardedExpression(node) {
|
|
3385
|
+
if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
|
|
3386
|
+
return node.body;
|
|
3387
|
+
}
|
|
3388
|
+
if (!node.body || node.body.type !== "BlockStatement") {
|
|
3389
|
+
return null;
|
|
3390
|
+
}
|
|
3391
|
+
const statements = node.body.body.filter((statement) => statement.type !== "EmptyStatement" && !(statement.type === "ExpressionStatement" && statement.directive));
|
|
3392
|
+
if (statements.length !== 1) {
|
|
3393
|
+
return null;
|
|
3394
|
+
}
|
|
3395
|
+
const statement = statements[0];
|
|
3396
|
+
if (statement.type === "ReturnStatement") {
|
|
3397
|
+
return statement.argument;
|
|
3398
|
+
}
|
|
3399
|
+
return statement.type === "ExpressionStatement" ? statement.expression : null;
|
|
3400
|
+
}
|
|
3401
|
+
function forwardedCall(expression) {
|
|
3402
|
+
if (expression.type === "ParenthesizedExpression" || expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSTypeAssertion" || expression.type === "TSNonNullExpression" || expression.type === "ChainExpression") {
|
|
3403
|
+
return forwardedCall(expression.expression);
|
|
3404
|
+
}
|
|
3405
|
+
if (expression.type === "AwaitExpression") {
|
|
3406
|
+
return forwardedCall(expression.argument);
|
|
3407
|
+
}
|
|
3408
|
+
return expression.type === "CallExpression" ? expression : null;
|
|
3409
|
+
}
|
|
3410
|
+
var noTrivialFunctions = defineRule30({
|
|
1726
3411
|
meta: {
|
|
1727
3412
|
type: "problem",
|
|
1728
3413
|
schema: [{
|
|
1729
3414
|
type: "object",
|
|
1730
3415
|
additionalProperties: false,
|
|
1731
3416
|
properties: {
|
|
1732
|
-
allowPattern: { type: "string" }
|
|
3417
|
+
allowPattern: { type: "string" },
|
|
3418
|
+
allowCallees: { type: "array", items: { type: "string" } },
|
|
3419
|
+
allowAsync: { type: "boolean" }
|
|
1733
3420
|
}
|
|
1734
3421
|
}],
|
|
1735
3422
|
messages: {
|
|
@@ -1737,16 +3424,29 @@ var noTrivialFunctions = defineRule19({
|
|
|
1737
3424
|
}
|
|
1738
3425
|
},
|
|
1739
3426
|
createOnce(context) {
|
|
3427
|
+
let allowed = null;
|
|
3428
|
+
let allowedCallees = [];
|
|
3429
|
+
let allowAsync = false;
|
|
1740
3430
|
return {
|
|
3431
|
+
before() {
|
|
3432
|
+
const { allowPattern, allowCallees: calleePatterns = [], allowAsync: asyncAllowed = false } = optionsFirst(context, {});
|
|
3433
|
+
allowed = allowPattern ? new RegExp(allowPattern) : null;
|
|
3434
|
+
allowedCallees = calleePatterns.map((pattern) => new RegExp(pattern));
|
|
3435
|
+
allowAsync = asyncAllowed;
|
|
3436
|
+
},
|
|
1741
3437
|
Program(program) {
|
|
1742
|
-
const { allowPattern } = optionsFirst(context, {});
|
|
1743
|
-
const allowed = allowPattern ? new RegExp(allowPattern) : null;
|
|
1744
3438
|
const seen = new Set;
|
|
1745
3439
|
for (const item of [...exportsCollectFunctions(program), ...declarationsTopLevelUnexportedFunctions(program)]) {
|
|
1746
3440
|
const key = `${item.name}:${item.node.start}:${item.node.end}`;
|
|
1747
3441
|
if (seen.has(key) || allowed?.test(item.name) || !declarationsIsTrivialFunction(item.node)) {
|
|
1748
3442
|
continue;
|
|
1749
3443
|
}
|
|
3444
|
+
const expression = forwardedExpression(item.node);
|
|
3445
|
+
const call = expression && forwardedCall(expression);
|
|
3446
|
+
const path = call && astDottedName(call.callee);
|
|
3447
|
+
if (allowAsync && item.node.async && call || path && allowedCallees.some((pattern) => pattern.test(path))) {
|
|
3448
|
+
continue;
|
|
3449
|
+
}
|
|
1750
3450
|
seen.add(key);
|
|
1751
3451
|
context.report({
|
|
1752
3452
|
node: item.node,
|
|
@@ -1760,8 +3460,8 @@ var noTrivialFunctions = defineRule19({
|
|
|
1760
3460
|
});
|
|
1761
3461
|
|
|
1762
3462
|
// src/rules/no-type-declarations.ts
|
|
1763
|
-
import { defineRule as
|
|
1764
|
-
var noTypeDeclarations =
|
|
3463
|
+
import { defineRule as defineRule31 } from "@oxlint/plugins";
|
|
3464
|
+
var noTypeDeclarations = defineRule31({
|
|
1765
3465
|
meta: {
|
|
1766
3466
|
type: "problem",
|
|
1767
3467
|
schema: [{
|
|
@@ -1776,28 +3476,31 @@ var noTypeDeclarations = defineRule20({
|
|
|
1776
3476
|
}
|
|
1777
3477
|
},
|
|
1778
3478
|
createOnce(context) {
|
|
3479
|
+
let allowed = null;
|
|
3480
|
+
function check(node) {
|
|
3481
|
+
if (allowed?.test(node.id.name)) {
|
|
3482
|
+
return;
|
|
3483
|
+
}
|
|
3484
|
+
context.report({
|
|
3485
|
+
node,
|
|
3486
|
+
messageId: "typeDeclaration",
|
|
3487
|
+
data: { name: node.id.name }
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
1779
3490
|
return {
|
|
1780
|
-
|
|
3491
|
+
before() {
|
|
1781
3492
|
const { allowPattern } = optionsFirst(context, {});
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
}
|
|
1787
|
-
context.report({
|
|
1788
|
-
node: item.node,
|
|
1789
|
-
messageId: "typeDeclaration",
|
|
1790
|
-
data: { name: item.name }
|
|
1791
|
-
});
|
|
1792
|
-
}
|
|
1793
|
-
}
|
|
3493
|
+
allowed = allowPattern ? new RegExp(allowPattern) : null;
|
|
3494
|
+
},
|
|
3495
|
+
TSTypeAliasDeclaration: check,
|
|
3496
|
+
TSInterfaceDeclaration: check
|
|
1794
3497
|
};
|
|
1795
3498
|
}
|
|
1796
3499
|
});
|
|
1797
3500
|
|
|
1798
3501
|
// src/rules/no-unescaped-like.ts
|
|
1799
|
-
import { defineRule as
|
|
1800
|
-
var noUnescapedLike =
|
|
3502
|
+
import { defineRule as defineRule32 } from "@oxlint/plugins";
|
|
3503
|
+
var noUnescapedLike = defineRule32({
|
|
1801
3504
|
meta: {
|
|
1802
3505
|
type: "problem",
|
|
1803
3506
|
schema: [{
|
|
@@ -1816,21 +3519,21 @@ var noUnescapedLike = defineRule21({
|
|
|
1816
3519
|
let sanitized = new Set;
|
|
1817
3520
|
return {
|
|
1818
3521
|
Program(program) {
|
|
1819
|
-
const { sanitizers
|
|
1820
|
-
sanitized = allowSanitizedBindings ? sanitizersConfiguredBindings(program, new Set(
|
|
3522
|
+
const { sanitizers, allowSanitizedBindings = true } = optionsFirst(context);
|
|
3523
|
+
sanitized = allowSanitizedBindings ? sanitizersConfiguredBindings(program, new Set(sanitizers)) : new Set;
|
|
1821
3524
|
},
|
|
1822
3525
|
CallExpression(node) {
|
|
1823
3526
|
if (node.callee.type !== "MemberExpression") {
|
|
1824
3527
|
return;
|
|
1825
3528
|
}
|
|
1826
|
-
const { methods, sanitizers
|
|
3529
|
+
const { methods, sanitizers } = optionsFirst(context);
|
|
1827
3530
|
const method = astStaticMemberName(node.callee);
|
|
1828
3531
|
if (!method || !methods.includes(method)) {
|
|
1829
3532
|
return;
|
|
1830
3533
|
}
|
|
1831
3534
|
const value = node.arguments.at(-1);
|
|
1832
3535
|
const allowedIdentifier = value?.type === "Identifier" && sanitized.has(value.name);
|
|
1833
|
-
if (!sanitizersCallsConfigured(value, new Set(
|
|
3536
|
+
if (!sanitizersCallsConfigured(value, new Set(sanitizers)) && !allowedIdentifier) {
|
|
1834
3537
|
context.report({
|
|
1835
3538
|
node: value ?? node,
|
|
1836
3539
|
messageId: "unescaped",
|
|
@@ -1842,31 +3545,150 @@ var noUnescapedLike = defineRule21({
|
|
|
1842
3545
|
}
|
|
1843
3546
|
});
|
|
1844
3547
|
|
|
3548
|
+
// src/rules/object-multiline.ts
|
|
3549
|
+
import { defineRule as defineRule33 } from "@oxlint/plugins";
|
|
3550
|
+
function objectMultilineIsCallArg(node) {
|
|
3551
|
+
const parent = node.parent;
|
|
3552
|
+
return (parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node);
|
|
3553
|
+
}
|
|
3554
|
+
function objectMultilineIsWhitespace(text) {
|
|
3555
|
+
return /^[\t\n\r\f ]*$/.test(text);
|
|
3556
|
+
}
|
|
3557
|
+
function objectMultilineGaps(text, node) {
|
|
3558
|
+
const gaps = [];
|
|
3559
|
+
const close = node.range[1] - 1;
|
|
3560
|
+
const first = node.properties[0];
|
|
3561
|
+
if (!objectMultilineIsWhitespace(text.slice(node.range[0] + 1, first.range[0]))) {
|
|
3562
|
+
return null;
|
|
3563
|
+
}
|
|
3564
|
+
gaps.push([node.range[0] + 1, first.range[0]]);
|
|
3565
|
+
for (let index = 0;index < node.properties.length; index++) {
|
|
3566
|
+
const property = node.properties[index];
|
|
3567
|
+
const next = node.properties[index + 1];
|
|
3568
|
+
const after = property.range[1];
|
|
3569
|
+
const limit = next ? next.range[0] : close;
|
|
3570
|
+
const between = text.slice(after, limit);
|
|
3571
|
+
const comma = between.indexOf(",");
|
|
3572
|
+
if (comma === -1) {
|
|
3573
|
+
if (next || !objectMultilineIsWhitespace(between)) {
|
|
3574
|
+
return null;
|
|
3575
|
+
}
|
|
3576
|
+
gaps.push([after, close]);
|
|
3577
|
+
continue;
|
|
3578
|
+
}
|
|
3579
|
+
if (!objectMultilineIsWhitespace(between.slice(0, comma)) || !objectMultilineIsWhitespace(between.slice(comma + 1))) {
|
|
3580
|
+
return null;
|
|
3581
|
+
}
|
|
3582
|
+
gaps.push([after + comma + 1, limit]);
|
|
3583
|
+
}
|
|
3584
|
+
return gaps;
|
|
3585
|
+
}
|
|
3586
|
+
var objectMultiline = defineRule33({
|
|
3587
|
+
meta: {
|
|
3588
|
+
type: "layout",
|
|
3589
|
+
fixable: "whitespace",
|
|
3590
|
+
schema: [{
|
|
3591
|
+
type: "object",
|
|
3592
|
+
additionalProperties: false,
|
|
3593
|
+
properties: {
|
|
3594
|
+
minProperties: { type: "integer", minimum: 1, default: 3 },
|
|
3595
|
+
scope: { type: "string", enum: ["call-args", "all"], default: "call-args" },
|
|
3596
|
+
indent: { ...layoutIndentSchema, default: 2 }
|
|
3597
|
+
}
|
|
3598
|
+
}],
|
|
3599
|
+
messages: {
|
|
3600
|
+
callArgs: "Object with {{min}}+ properties in a call should have each property on its own line.",
|
|
3601
|
+
always: "Object with {{min}}+ properties should have each property on its own line."
|
|
3602
|
+
}
|
|
3603
|
+
},
|
|
3604
|
+
createOnce(context) {
|
|
3605
|
+
let minProperties = 3;
|
|
3606
|
+
let scope = "call-args";
|
|
3607
|
+
let indentUnit = layoutIndentUnit();
|
|
3608
|
+
let newline = `
|
|
3609
|
+
`;
|
|
3610
|
+
return {
|
|
3611
|
+
before() {
|
|
3612
|
+
const options = optionsFirst(context, {});
|
|
3613
|
+
minProperties = options.minProperties ?? 3;
|
|
3614
|
+
scope = options.scope ?? "call-args";
|
|
3615
|
+
indentUnit = layoutIndentUnit(options.indent);
|
|
3616
|
+
newline = layoutNewline(context.sourceCode.text);
|
|
3617
|
+
},
|
|
3618
|
+
ObjectExpression(node) {
|
|
3619
|
+
if (node.properties.length < minProperties || node.loc.start.line !== node.loc.end.line) {
|
|
3620
|
+
return;
|
|
3621
|
+
}
|
|
3622
|
+
if (scope === "call-args" && !objectMultilineIsCallArg(node)) {
|
|
3623
|
+
return;
|
|
3624
|
+
}
|
|
3625
|
+
const source = context.sourceCode;
|
|
3626
|
+
const gaps = source.getCommentsInside(node).length > 0 ? null : objectMultilineGaps(source.text, node);
|
|
3627
|
+
const baseIndent = layoutLineIndent(source.text, node.range[0]);
|
|
3628
|
+
const propIndent = `${baseIndent}${indentUnit}`;
|
|
3629
|
+
context.report({
|
|
3630
|
+
node,
|
|
3631
|
+
messageId: scope === "all" ? "always" : "callArgs",
|
|
3632
|
+
data: { min: String(minProperties) },
|
|
3633
|
+
fix(fixer) {
|
|
3634
|
+
if (!gaps) {
|
|
3635
|
+
return null;
|
|
3636
|
+
}
|
|
3637
|
+
return gaps.map((gap, index) => fixer.replaceTextRange(gap, `${newline}${index === gaps.length - 1 ? baseIndent : propIndent}`));
|
|
3638
|
+
}
|
|
3639
|
+
});
|
|
3640
|
+
}
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
});
|
|
3644
|
+
|
|
1845
3645
|
// src/rules/only-export-components.ts
|
|
1846
|
-
import { defineRule as
|
|
1847
|
-
var onlyExportComponents =
|
|
3646
|
+
import { defineRule as defineRule34 } from "@oxlint/plugins";
|
|
3647
|
+
var onlyExportComponents = defineRule34({
|
|
1848
3648
|
meta: {
|
|
1849
3649
|
type: "problem",
|
|
1850
3650
|
schema: [{
|
|
1851
3651
|
type: "object",
|
|
1852
3652
|
additionalProperties: false,
|
|
1853
3653
|
properties: {
|
|
1854
|
-
matchFileName: { type: "boolean" }
|
|
3654
|
+
matchFileName: { type: "boolean" },
|
|
3655
|
+
allowTypeExports: { type: "boolean" },
|
|
3656
|
+
denyTypePattern: { type: "string" }
|
|
1855
3657
|
}
|
|
1856
3658
|
}],
|
|
1857
3659
|
messages: {
|
|
1858
3660
|
nonComponent: "Export '{{name}}' must be a React component or type.",
|
|
1859
|
-
nameMismatch: "Component export '{{name}}' must match '{{expected}}' or its prefix."
|
|
3661
|
+
nameMismatch: "Component export '{{name}}' must match '{{expected}}' or its prefix.",
|
|
3662
|
+
typeExport: "Type export '{{name}}' is not allowed.",
|
|
3663
|
+
deniedType: "Type export '{{name}}' is denied by denyTypePattern."
|
|
1860
3664
|
}
|
|
1861
3665
|
},
|
|
1862
3666
|
createOnce(context) {
|
|
1863
3667
|
return {
|
|
1864
3668
|
Program(program) {
|
|
1865
|
-
const {
|
|
3669
|
+
const {
|
|
3670
|
+
matchFileName = true,
|
|
3671
|
+
allowTypeExports = true,
|
|
3672
|
+
denyTypePattern
|
|
3673
|
+
} = optionsFirst(context, {});
|
|
1866
3674
|
const expected = reactComponentsPascalFromBasename(namingFileBasename(context.filename));
|
|
3675
|
+
const denied = denyTypePattern ? new RegExp(denyTypePattern) : null;
|
|
1867
3676
|
const components = [];
|
|
1868
3677
|
for (const candidate of reactComponentsExportCandidates(program)) {
|
|
1869
3678
|
if (candidate.typeOnly) {
|
|
3679
|
+
if (!allowTypeExports) {
|
|
3680
|
+
context.report({
|
|
3681
|
+
node: candidate.node,
|
|
3682
|
+
messageId: "typeExport",
|
|
3683
|
+
data: { name: candidate.name, expected }
|
|
3684
|
+
});
|
|
3685
|
+
} else if (denied?.test(candidate.name)) {
|
|
3686
|
+
context.report({
|
|
3687
|
+
node: candidate.node,
|
|
3688
|
+
messageId: "deniedType",
|
|
3689
|
+
data: { name: candidate.name, expected }
|
|
3690
|
+
});
|
|
3691
|
+
}
|
|
1870
3692
|
continue;
|
|
1871
3693
|
}
|
|
1872
3694
|
if (!reactComponentsIsLike(program, candidate)) {
|
|
@@ -1899,9 +3721,265 @@ var onlyExportComponents = defineRule22({
|
|
|
1899
3721
|
}
|
|
1900
3722
|
});
|
|
1901
3723
|
|
|
3724
|
+
// src/rules/only-export-constants.ts
|
|
3725
|
+
import { defineRule as defineRule35 } from "@oxlint/plugins";
|
|
3726
|
+
function bindingNames(pattern) {
|
|
3727
|
+
switch (pattern.type) {
|
|
3728
|
+
case "Identifier":
|
|
3729
|
+
return [pattern.name];
|
|
3730
|
+
case "AssignmentPattern":
|
|
3731
|
+
return bindingNames(pattern.left);
|
|
3732
|
+
case "RestElement":
|
|
3733
|
+
return bindingNames(pattern.argument);
|
|
3734
|
+
case "ArrayPattern":
|
|
3735
|
+
return pattern.elements.flatMap((element) => element ? bindingNames(element) : []);
|
|
3736
|
+
case "ObjectPattern":
|
|
3737
|
+
return pattern.properties.flatMap((property) => bindingNames(property.type === "RestElement" ? property : property.value));
|
|
3738
|
+
}
|
|
3739
|
+
}
|
|
3740
|
+
function moduleName(node) {
|
|
3741
|
+
return node.type === "Identifier" ? node.name : node.value;
|
|
3742
|
+
}
|
|
3743
|
+
function unwrap(expression) {
|
|
3744
|
+
if (expression.type === "ParenthesizedExpression" || expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSTypeAssertion" || expression.type === "TSNonNullExpression" || expression.type === "TSInstantiationExpression") {
|
|
3745
|
+
return unwrap(expression.expression);
|
|
3746
|
+
}
|
|
3747
|
+
return expression;
|
|
3748
|
+
}
|
|
3749
|
+
function collectBindings(program) {
|
|
3750
|
+
const bindings = new Map;
|
|
3751
|
+
for (const statement of program.body) {
|
|
3752
|
+
if (statement.type === "ImportDeclaration") {
|
|
3753
|
+
for (const specifier of statement.specifiers) {
|
|
3754
|
+
bindings.set(specifier.local.name, {
|
|
3755
|
+
kind: statement.importKind === "type" || specifier.type === "ImportSpecifier" && specifier.importKind === "type" ? "type" : "import"
|
|
3756
|
+
});
|
|
3757
|
+
}
|
|
3758
|
+
continue;
|
|
3759
|
+
}
|
|
3760
|
+
const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
|
|
3761
|
+
if (declaration?.type === "TSImportEqualsDeclaration") {
|
|
3762
|
+
bindings.set(declaration.id.name, { kind: declaration.importKind === "type" ? "type" : "import" });
|
|
3763
|
+
} else if (declaration?.type === "VariableDeclaration") {
|
|
3764
|
+
for (const item of declaration.declarations) {
|
|
3765
|
+
for (const name of bindingNames(item.id)) {
|
|
3766
|
+
bindings.set(name, {
|
|
3767
|
+
kind: declaration.kind === "const" ? "const" : "other",
|
|
3768
|
+
value: item.id.type === "Identifier" ? item.init : undefined
|
|
3769
|
+
});
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
} else if (declaration && "id" in declaration && declaration.id?.type === "Identifier") {
|
|
3773
|
+
bindings.set(declaration.id.name, {
|
|
3774
|
+
kind: declaration.type === "TSTypeAliasDeclaration" || declaration.type === "TSInterfaceDeclaration" ? "type" : declaration.type === "FunctionDeclaration" || declaration.type === "TSDeclareFunction" ? "function" : declaration.type === "ClassDeclaration" ? "class" : "other"
|
|
3775
|
+
});
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
return bindings;
|
|
3779
|
+
}
|
|
3780
|
+
var onlyExportConstants = defineRule35({
|
|
3781
|
+
meta: {
|
|
3782
|
+
type: "problem",
|
|
3783
|
+
schema: [{
|
|
3784
|
+
type: "object",
|
|
3785
|
+
additionalProperties: false,
|
|
3786
|
+
properties: {
|
|
3787
|
+
allowFunctionValues: { type: "boolean" },
|
|
3788
|
+
allowTypeExports: { type: "boolean" },
|
|
3789
|
+
allowReExports: { type: "boolean" }
|
|
3790
|
+
}
|
|
3791
|
+
}],
|
|
3792
|
+
messages: {
|
|
3793
|
+
nonConstant: "Export '{{name}}' must be a local const binding with an allowed value; types and re-exports require explicit options."
|
|
3794
|
+
}
|
|
3795
|
+
},
|
|
3796
|
+
createOnce(context) {
|
|
3797
|
+
return {
|
|
3798
|
+
Program(program) {
|
|
3799
|
+
const { allowFunctionValues = false, allowTypeExports = false, allowReExports = false } = optionsFirst(context, {});
|
|
3800
|
+
const bindings = collectBindings(program);
|
|
3801
|
+
function hasForbiddenValue(binding, seen = new Set) {
|
|
3802
|
+
if (seen.has(binding) || binding.kind === "class") {
|
|
3803
|
+
return true;
|
|
3804
|
+
}
|
|
3805
|
+
if (binding.kind === "function") {
|
|
3806
|
+
return !allowFunctionValues;
|
|
3807
|
+
}
|
|
3808
|
+
if (!binding.value) {
|
|
3809
|
+
return false;
|
|
3810
|
+
}
|
|
3811
|
+
seen.add(binding);
|
|
3812
|
+
const value = unwrap(binding.value);
|
|
3813
|
+
if (value.type === "ArrowFunctionExpression" || value.type === "FunctionExpression") {
|
|
3814
|
+
return !allowFunctionValues;
|
|
3815
|
+
}
|
|
3816
|
+
if (value.type === "ClassExpression") {
|
|
3817
|
+
return true;
|
|
3818
|
+
}
|
|
3819
|
+
const target = value.type === "Identifier" ? bindings.get(value.name) : undefined;
|
|
3820
|
+
return target ? hasForbiddenValue(target, seen) : false;
|
|
3821
|
+
}
|
|
3822
|
+
function check(node, name, localName, typeOnly = false, reExport = false) {
|
|
3823
|
+
const binding = localName ? bindings.get(localName) : undefined;
|
|
3824
|
+
const allowed = typeOnly || !reExport && binding?.kind === "type" ? allowTypeExports : reExport || binding?.kind === "import" ? allowReExports : binding?.kind === "const" && !hasForbiddenValue(binding);
|
|
3825
|
+
if (!allowed) {
|
|
3826
|
+
context.report({ node, messageId: "nonConstant", data: { name } });
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3829
|
+
for (const statement of program.body) {
|
|
3830
|
+
if (statement.type === "ExportAllDeclaration") {
|
|
3831
|
+
check(statement, "*", undefined, statement.exportKind === "type", true);
|
|
3832
|
+
} else if (statement.type === "ExportDefaultDeclaration") {
|
|
3833
|
+
const declaration = statement.declaration;
|
|
3834
|
+
check(statement, "default", declaration.type === "Identifier" ? declaration.name : undefined, declaration.type === "TSInterfaceDeclaration");
|
|
3835
|
+
} else if (statement.type === "TSExportAssignment") {
|
|
3836
|
+
check(statement, "export =");
|
|
3837
|
+
} else if (statement.type === "TSNamespaceExportDeclaration") {
|
|
3838
|
+
check(statement, statement.id.name);
|
|
3839
|
+
} else if (statement.type === "ExportNamedDeclaration") {
|
|
3840
|
+
const declaration = statement.declaration;
|
|
3841
|
+
if (declaration?.type === "VariableDeclaration") {
|
|
3842
|
+
for (const item of declaration.declarations) {
|
|
3843
|
+
for (const name of bindingNames(item.id)) {
|
|
3844
|
+
check(item, name, name);
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
} else if (declaration) {
|
|
3848
|
+
const name = "id" in declaration && declaration.id?.type === "Identifier" ? declaration.id.name : "<anonymous>";
|
|
3849
|
+
check(declaration, name, name);
|
|
3850
|
+
}
|
|
3851
|
+
for (const specifier of statement.specifiers) {
|
|
3852
|
+
check(specifier, moduleName(specifier.exported), moduleName(specifier.local), statement.exportKind === "type" || specifier.exportKind === "type", Boolean(statement.source));
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
}
|
|
3856
|
+
}
|
|
3857
|
+
};
|
|
3858
|
+
}
|
|
3859
|
+
});
|
|
3860
|
+
|
|
3861
|
+
// src/rules/padding-between-statements.ts
|
|
3862
|
+
import { defineRule as defineRule36 } from "@oxlint/plugins";
|
|
3863
|
+
function paddingKind(node) {
|
|
3864
|
+
if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
|
|
3865
|
+
return node.declaration ? paddingKind(node.declaration) : "other";
|
|
3866
|
+
}
|
|
3867
|
+
switch (node.type) {
|
|
3868
|
+
case "VariableDeclaration":
|
|
3869
|
+
return "variable";
|
|
3870
|
+
case "FunctionDeclaration":
|
|
3871
|
+
case "ClassDeclaration":
|
|
3872
|
+
return "block";
|
|
3873
|
+
case "IfStatement":
|
|
3874
|
+
case "ForStatement":
|
|
3875
|
+
case "ForInStatement":
|
|
3876
|
+
case "ForOfStatement":
|
|
3877
|
+
case "WhileStatement":
|
|
3878
|
+
case "DoWhileStatement":
|
|
3879
|
+
case "SwitchStatement":
|
|
3880
|
+
case "TryStatement":
|
|
3881
|
+
return "control";
|
|
3882
|
+
case "ReturnStatement":
|
|
3883
|
+
return "return";
|
|
3884
|
+
default:
|
|
3885
|
+
return "other";
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
function paddingHasBlankLine(gap) {
|
|
3889
|
+
const lines = gap.split(`
|
|
3890
|
+
`);
|
|
3891
|
+
for (let index = 1;index < lines.length - 1; index++) {
|
|
3892
|
+
if (/^[\t\r ]*$/.test(lines[index])) {
|
|
3893
|
+
return true;
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
return false;
|
|
3897
|
+
}
|
|
3898
|
+
function paddingBlankLineFix(sourceText, comments, prev, curr, newline) {
|
|
3899
|
+
const gap = sourceText.slice(prev.range[1], curr.range[0]);
|
|
3900
|
+
const match = /\r?\n/.exec(gap);
|
|
3901
|
+
if (!match) {
|
|
3902
|
+
return null;
|
|
3903
|
+
}
|
|
3904
|
+
const newlineStart = prev.range[1] + match.index;
|
|
3905
|
+
const newlineEnd = newlineStart + match[0].length;
|
|
3906
|
+
if (comments.some((comment) => comment.range[0] < newlineEnd && comment.range[1] > newlineStart)) {
|
|
3907
|
+
return null;
|
|
3908
|
+
}
|
|
3909
|
+
if (comments.some((comment) => comment.loc.start.line === prev.loc.end.line && /^\s*[A-Za-z][\w-]*-disable-next-line\b/.test(commentsNormalizedValue(comment)))) {
|
|
3910
|
+
return null;
|
|
3911
|
+
}
|
|
3912
|
+
return { newlineEnd, newline };
|
|
3913
|
+
}
|
|
3914
|
+
var paddingBetweenStatements = defineRule36({
|
|
3915
|
+
meta: {
|
|
3916
|
+
type: "layout",
|
|
3917
|
+
fixable: "whitespace",
|
|
3918
|
+
schema: [{
|
|
3919
|
+
type: "object",
|
|
3920
|
+
additionalProperties: false,
|
|
3921
|
+
properties: {
|
|
3922
|
+
returnMinStatements: { type: "integer", minimum: 1, default: 3 }
|
|
3923
|
+
}
|
|
3924
|
+
}],
|
|
3925
|
+
messages: {
|
|
3926
|
+
variableAndBlock: "Expected a blank line between a variable declaration and a function/class.",
|
|
3927
|
+
beforeBlock: "Expected a blank line before a function/class declaration.",
|
|
3928
|
+
afterBlock: "Expected a blank line after a function/class declaration.",
|
|
3929
|
+
variableAndControl: "Expected a blank line between a variable declaration and control flow.",
|
|
3930
|
+
controlAndVariable: "Expected a blank line between control flow and a variable declaration.",
|
|
3931
|
+
consecutiveControl: "Expected a blank line between control flow statements.",
|
|
3932
|
+
beforeReturn: "Expected a blank line before return statement."
|
|
3933
|
+
}
|
|
3934
|
+
},
|
|
3935
|
+
createOnce(context) {
|
|
3936
|
+
let returnMinStatements = 3;
|
|
3937
|
+
let newline = `
|
|
3938
|
+
`;
|
|
3939
|
+
let source;
|
|
3940
|
+
function checkStatements(statements) {
|
|
3941
|
+
for (let index = 1;index < statements.length; index++) {
|
|
3942
|
+
const prev = statements[index - 1];
|
|
3943
|
+
const curr = statements[index];
|
|
3944
|
+
const gap = source.text.slice(prev.range[1], curr.range[0]);
|
|
3945
|
+
if (!/\r?\n/.test(gap) || paddingHasBlankLine(gap)) {
|
|
3946
|
+
continue;
|
|
3947
|
+
}
|
|
3948
|
+
const prevKind = paddingKind(prev);
|
|
3949
|
+
const currKind = paddingKind(curr);
|
|
3950
|
+
const messageId = prevKind === "variable" && currKind === "block" || prevKind === "block" && currKind === "variable" ? "variableAndBlock" : currKind === "block" ? "beforeBlock" : prevKind === "block" ? "afterBlock" : prevKind === "variable" && currKind === "control" ? "variableAndControl" : prevKind === "control" && currKind === "variable" ? "controlAndVariable" : prevKind === "control" && currKind === "control" ? "consecutiveControl" : currKind === "return" && prevKind !== "variable" && statements.length >= returnMinStatements ? "beforeReturn" : null;
|
|
3951
|
+
if (!messageId) {
|
|
3952
|
+
continue;
|
|
3953
|
+
}
|
|
3954
|
+
const comments = source.getCommentsAfter(prev).filter((comment) => comment.range[1] <= curr.range[0]);
|
|
3955
|
+
const insert = paddingBlankLineFix(source.text, comments, prev, curr, newline);
|
|
3956
|
+
context.report({
|
|
3957
|
+
node: curr,
|
|
3958
|
+
messageId,
|
|
3959
|
+
fix: insert ? (fixer) => fixer.insertTextAfterRange([insert.newlineEnd, insert.newlineEnd], insert.newline) : undefined
|
|
3960
|
+
});
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
return {
|
|
3964
|
+
before() {
|
|
3965
|
+
const options = optionsFirst(context, {});
|
|
3966
|
+
returnMinStatements = options.returnMinStatements ?? 3;
|
|
3967
|
+
source = context.sourceCode;
|
|
3968
|
+
newline = layoutNewline(source.text);
|
|
3969
|
+
},
|
|
3970
|
+
Program(node) {
|
|
3971
|
+
checkStatements(node.body);
|
|
3972
|
+
},
|
|
3973
|
+
BlockStatement(node) {
|
|
3974
|
+
checkStatements(node.body);
|
|
3975
|
+
}
|
|
3976
|
+
};
|
|
3977
|
+
}
|
|
3978
|
+
});
|
|
3979
|
+
|
|
1902
3980
|
// src/rules/require-file-factory.ts
|
|
1903
|
-
import { defineRule as
|
|
1904
|
-
var requireFileFactory =
|
|
3981
|
+
import { defineRule as defineRule37 } from "@oxlint/plugins";
|
|
3982
|
+
var requireFileFactory = defineRule37({
|
|
1905
3983
|
meta: {
|
|
1906
3984
|
type: "problem",
|
|
1907
3985
|
schema: [{
|
|
@@ -1947,24 +4025,46 @@ var requireFileFactory = defineRule23({
|
|
|
1947
4025
|
});
|
|
1948
4026
|
|
|
1949
4027
|
// src/rules/require-object-params.ts
|
|
1950
|
-
import { defineRule as
|
|
1951
|
-
var requireObjectParams =
|
|
4028
|
+
import { defineRule as defineRule38 } from "@oxlint/plugins";
|
|
4029
|
+
var requireObjectParams = defineRule38({
|
|
1952
4030
|
meta: {
|
|
1953
4031
|
type: "problem",
|
|
1954
|
-
schema: [
|
|
4032
|
+
schema: [{
|
|
4033
|
+
type: "object",
|
|
4034
|
+
additionalProperties: false,
|
|
4035
|
+
properties: {
|
|
4036
|
+
maxParams: { type: "integer", minimum: 1 },
|
|
4037
|
+
allDeclarations: { type: "boolean" },
|
|
4038
|
+
allowPattern: { type: "string" }
|
|
4039
|
+
}
|
|
4040
|
+
}],
|
|
1955
4041
|
messages: {
|
|
1956
|
-
objectParams: "
|
|
4042
|
+
objectParams: "{{label}} {{name}} must accept at most {{maxParams}} object parameter{{s}}."
|
|
1957
4043
|
}
|
|
1958
4044
|
},
|
|
1959
4045
|
createOnce(context) {
|
|
1960
4046
|
return {
|
|
1961
4047
|
Program(program) {
|
|
1962
|
-
|
|
1963
|
-
|
|
4048
|
+
const { maxParams = 1, allDeclarations = false, allowPattern } = optionsFirst(context, {});
|
|
4049
|
+
const allowed = allowPattern ? new RegExp(allowPattern) : null;
|
|
4050
|
+
const seen = new Set;
|
|
4051
|
+
const functions = allDeclarations ? [...exportsCollectFunctions(program), ...declarationsTopLevelUnexportedFunctions(program)] : exportsCollectFunctions(program);
|
|
4052
|
+
for (const item of functions) {
|
|
4053
|
+
const key = `${item.name}:${item.node.start}:${item.node.end}`;
|
|
4054
|
+
if (seen.has(key) || allowed?.test(item.name)) {
|
|
4055
|
+
continue;
|
|
4056
|
+
}
|
|
4057
|
+
seen.add(key);
|
|
4058
|
+
if (item.node.params.length > maxParams || item.node.params.some((param) => !declarationsIsObjectParam(param))) {
|
|
1964
4059
|
context.report({
|
|
1965
4060
|
node: item.node,
|
|
1966
4061
|
messageId: "objectParams",
|
|
1967
|
-
data: {
|
|
4062
|
+
data: {
|
|
4063
|
+
label: allDeclarations ? "Function" : "Exported function",
|
|
4064
|
+
name: item.name,
|
|
4065
|
+
maxParams: maxParams === 1 ? "one" : String(maxParams),
|
|
4066
|
+
s: maxParams === 1 ? "" : "s"
|
|
4067
|
+
}
|
|
1968
4068
|
});
|
|
1969
4069
|
}
|
|
1970
4070
|
}
|
|
@@ -1974,8 +4074,8 @@ var requireObjectParams = defineRule24({
|
|
|
1974
4074
|
});
|
|
1975
4075
|
|
|
1976
4076
|
// src/rules/require-orpc-output.ts
|
|
1977
|
-
import { defineRule as
|
|
1978
|
-
var requireOrpcOutput =
|
|
4077
|
+
import { defineRule as defineRule39 } from "@oxlint/plugins";
|
|
4078
|
+
var requireOrpcOutput = defineRule39({
|
|
1979
4079
|
meta: {
|
|
1980
4080
|
type: "problem",
|
|
1981
4081
|
schema: [{
|
|
@@ -2017,8 +4117,8 @@ var requireOrpcOutput = defineRule25({
|
|
|
2017
4117
|
});
|
|
2018
4118
|
|
|
2019
4119
|
// src/rules/require-paired-call.ts
|
|
2020
|
-
import { defineRule as
|
|
2021
|
-
var requirePairedCall =
|
|
4120
|
+
import { defineRule as defineRule40 } from "@oxlint/plugins";
|
|
4121
|
+
var requirePairedCall = defineRule40({
|
|
2022
4122
|
meta: {
|
|
2023
4123
|
type: "problem",
|
|
2024
4124
|
schema: [{
|
|
@@ -2026,9 +4126,24 @@ var requirePairedCall = defineRule26({
|
|
|
2026
4126
|
additionalProperties: false,
|
|
2027
4127
|
properties: {
|
|
2028
4128
|
when: { type: "string" },
|
|
2029
|
-
require: { type: "string" }
|
|
4129
|
+
require: { type: "string" },
|
|
4130
|
+
pairs: {
|
|
4131
|
+
type: "array",
|
|
4132
|
+
items: {
|
|
4133
|
+
type: "object",
|
|
4134
|
+
additionalProperties: false,
|
|
4135
|
+
properties: {
|
|
4136
|
+
when: { type: "string" },
|
|
4137
|
+
require: { type: "string" }
|
|
4138
|
+
},
|
|
4139
|
+
required: ["when", "require"]
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
2030
4142
|
},
|
|
2031
|
-
|
|
4143
|
+
anyOf: [
|
|
4144
|
+
{ required: ["when", "require"] },
|
|
4145
|
+
{ required: ["pairs"] }
|
|
4146
|
+
]
|
|
2032
4147
|
}],
|
|
2033
4148
|
messages: {
|
|
2034
4149
|
paired: "{{when}} requires a call to {{require}} in the same file."
|
|
@@ -2036,45 +4151,50 @@ var requirePairedCall = defineRule26({
|
|
|
2036
4151
|
},
|
|
2037
4152
|
createOnce(context) {
|
|
2038
4153
|
let aliases = new Map;
|
|
2039
|
-
let
|
|
2040
|
-
let sawRequired = false;
|
|
4154
|
+
let pairs = [];
|
|
2041
4155
|
return {
|
|
2042
4156
|
before() {
|
|
4157
|
+
const options = optionsFirst(context);
|
|
4158
|
+
const configured = [
|
|
4159
|
+
...options.when && options.require ? [{ when: options.when, require: options.require }] : [],
|
|
4160
|
+
...options.pairs ?? []
|
|
4161
|
+
];
|
|
2043
4162
|
aliases = new Map;
|
|
2044
|
-
|
|
2045
|
-
sawRequired = false;
|
|
4163
|
+
pairs = configured.map((pair) => ({ ...pair, firstWhen: null, sawRequired: false }));
|
|
2046
4164
|
},
|
|
2047
4165
|
Program(program) {
|
|
2048
4166
|
aliases = astImportedCallAliases(program);
|
|
2049
4167
|
},
|
|
2050
4168
|
CallExpression(node) {
|
|
2051
|
-
const { when, require: require2 } = optionsFirst(context);
|
|
2052
4169
|
const name = astResolvedCallName(node, aliases);
|
|
2053
|
-
|
|
2054
|
-
firstWhen
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
4170
|
+
for (const pair of pairs) {
|
|
4171
|
+
if (name === pair.when && !pair.firstWhen) {
|
|
4172
|
+
pair.firstWhen = node;
|
|
4173
|
+
}
|
|
4174
|
+
if (name === pair.require) {
|
|
4175
|
+
pair.sawRequired = true;
|
|
4176
|
+
}
|
|
2058
4177
|
}
|
|
2059
4178
|
},
|
|
2060
4179
|
after() {
|
|
2061
|
-
|
|
2062
|
-
|
|
4180
|
+
for (const pair of pairs) {
|
|
4181
|
+
if (!pair.firstWhen || pair.sawRequired) {
|
|
4182
|
+
continue;
|
|
4183
|
+
}
|
|
4184
|
+
context.report({
|
|
4185
|
+
node: pair.firstWhen,
|
|
4186
|
+
messageId: "paired",
|
|
4187
|
+
data: { when: pair.when, require: pair.require }
|
|
4188
|
+
});
|
|
2063
4189
|
}
|
|
2064
|
-
const { when, require: require2 } = optionsFirst(context);
|
|
2065
|
-
context.report({
|
|
2066
|
-
node: firstWhen,
|
|
2067
|
-
messageId: "paired",
|
|
2068
|
-
data: { when, require: require2 }
|
|
2069
|
-
});
|
|
2070
4190
|
}
|
|
2071
4191
|
};
|
|
2072
4192
|
}
|
|
2073
4193
|
});
|
|
2074
4194
|
|
|
2075
4195
|
// src/rules/route-surface.ts
|
|
2076
|
-
import { defineRule as
|
|
2077
|
-
var routeSurface =
|
|
4196
|
+
import { defineRule as defineRule41 } from "@oxlint/plugins";
|
|
4197
|
+
var routeSurface = defineRule41({
|
|
2078
4198
|
meta: {
|
|
2079
4199
|
type: "problem",
|
|
2080
4200
|
schema: [{
|
|
@@ -2115,23 +4235,140 @@ var routeSurface = defineRule27({
|
|
|
2115
4235
|
}
|
|
2116
4236
|
});
|
|
2117
4237
|
|
|
4238
|
+
// src/rules/test-title-pattern.ts
|
|
4239
|
+
import { defineRule as defineRule42 } from "@oxlint/plugins";
|
|
4240
|
+
var TEST_MODIFIERS = new Set(["only", "skip", "concurrent", "todo", "each"]);
|
|
4241
|
+
function isConfiguredName(name, callees) {
|
|
4242
|
+
if (!name) {
|
|
4243
|
+
return false;
|
|
4244
|
+
}
|
|
4245
|
+
return callees.some((callee) => {
|
|
4246
|
+
if (name === callee) {
|
|
4247
|
+
return true;
|
|
4248
|
+
}
|
|
4249
|
+
if (!name.startsWith(`${callee}.`)) {
|
|
4250
|
+
return false;
|
|
4251
|
+
}
|
|
4252
|
+
return name.slice(callee.length + 1).split(".").every((part) => TEST_MODIFIERS.has(part));
|
|
4253
|
+
});
|
|
4254
|
+
}
|
|
4255
|
+
function isConfiguredCall(node, callees) {
|
|
4256
|
+
if (isConfiguredName(astDottedName(node.callee), callees)) {
|
|
4257
|
+
return true;
|
|
4258
|
+
}
|
|
4259
|
+
if (node.callee.type === "CallExpression") {
|
|
4260
|
+
return isConfiguredName(astDottedName(node.callee.callee), callees);
|
|
4261
|
+
}
|
|
4262
|
+
return node.callee.type === "TaggedTemplateExpression" && isConfiguredName(astDottedName(node.callee.tag), callees);
|
|
4263
|
+
}
|
|
4264
|
+
function titleText(node) {
|
|
4265
|
+
if (node.type === "Literal" && typeof node.value === "string") {
|
|
4266
|
+
return node.value;
|
|
4267
|
+
}
|
|
4268
|
+
if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
|
|
4269
|
+
return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;
|
|
4270
|
+
}
|
|
4271
|
+
}
|
|
4272
|
+
function regexMatches(regex, value) {
|
|
4273
|
+
regex.lastIndex = 0;
|
|
4274
|
+
return regex.test(value);
|
|
4275
|
+
}
|
|
4276
|
+
var testTitlePattern = defineRule42({
|
|
4277
|
+
meta: {
|
|
4278
|
+
type: "suggestion",
|
|
4279
|
+
schema: [{
|
|
4280
|
+
type: "object",
|
|
4281
|
+
additionalProperties: false,
|
|
4282
|
+
properties: {
|
|
4283
|
+
callees: { type: "array", items: { type: "string" } },
|
|
4284
|
+
forbid: { type: "string" },
|
|
4285
|
+
require: { type: "string" },
|
|
4286
|
+
flags: { type: "string", pattern: "^[dgimsuvy]*$" }
|
|
4287
|
+
}
|
|
4288
|
+
}],
|
|
4289
|
+
defaultOptions: [{
|
|
4290
|
+
callees: ["describe", "it", "test"],
|
|
4291
|
+
flags: ""
|
|
4292
|
+
}],
|
|
4293
|
+
messages: {
|
|
4294
|
+
forbidden: "Test title matches forbidden pattern '{{pattern}}'.",
|
|
4295
|
+
required: "Test title must match required pattern '{{pattern}}'."
|
|
4296
|
+
}
|
|
4297
|
+
},
|
|
4298
|
+
createOnce(context) {
|
|
4299
|
+
let callees = [];
|
|
4300
|
+
let forbid;
|
|
4301
|
+
let required;
|
|
4302
|
+
return {
|
|
4303
|
+
before() {
|
|
4304
|
+
const options = optionsFirst(context);
|
|
4305
|
+
callees = options.callees;
|
|
4306
|
+
const flags = options.flags ?? "";
|
|
4307
|
+
forbid = options.forbid === undefined ? undefined : { regex: new RegExp(options.forbid, flags), pattern: options.forbid };
|
|
4308
|
+
required = options.require === undefined ? undefined : { regex: new RegExp(options.require, flags), pattern: options.require };
|
|
4309
|
+
if (forbid === undefined && required === undefined) {
|
|
4310
|
+
return false;
|
|
4311
|
+
}
|
|
4312
|
+
},
|
|
4313
|
+
CallExpression(node) {
|
|
4314
|
+
if (!isConfiguredCall(node, callees)) {
|
|
4315
|
+
return;
|
|
4316
|
+
}
|
|
4317
|
+
const title = node.arguments[0];
|
|
4318
|
+
if (!title) {
|
|
4319
|
+
return;
|
|
4320
|
+
}
|
|
4321
|
+
const text = titleText(title);
|
|
4322
|
+
if (text === undefined) {
|
|
4323
|
+
return;
|
|
4324
|
+
}
|
|
4325
|
+
if (forbid && regexMatches(forbid.regex, text)) {
|
|
4326
|
+
context.report({
|
|
4327
|
+
node: title,
|
|
4328
|
+
messageId: "forbidden",
|
|
4329
|
+
data: { pattern: forbid.pattern }
|
|
4330
|
+
});
|
|
4331
|
+
}
|
|
4332
|
+
if (required && !regexMatches(required.regex, text)) {
|
|
4333
|
+
context.report({
|
|
4334
|
+
node: title,
|
|
4335
|
+
messageId: "required",
|
|
4336
|
+
data: { pattern: required.pattern }
|
|
4337
|
+
});
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
};
|
|
4341
|
+
}
|
|
4342
|
+
});
|
|
4343
|
+
|
|
2118
4344
|
// src/index.ts
|
|
2119
4345
|
var src_default = eslintCompatPlugin({
|
|
2120
4346
|
meta: { name: "arch" },
|
|
2121
4347
|
rules: {
|
|
4348
|
+
"call-array-multiline": callArrayMultiline,
|
|
4349
|
+
"chain-newline": chainNewline,
|
|
2122
4350
|
"declaration-name": declarationName,
|
|
2123
4351
|
"export-file-prefix": exportFilePrefix,
|
|
2124
4352
|
"export-name-pattern": exportNamePattern,
|
|
2125
4353
|
"filename-export-name": filenameExportName,
|
|
2126
4354
|
"filename-match": filenameMatch,
|
|
2127
4355
|
"folder-prefix": folderPrefix,
|
|
4356
|
+
"jsx-attributes-multiline": jsxAttributesMultiline,
|
|
4357
|
+
"key-value-same-line": keyValueSameLine,
|
|
4358
|
+
"no-comments": noComments,
|
|
2128
4359
|
"no-extra-exports": noExtraExports,
|
|
2129
4360
|
"no-extra-factory-keys": noExtraFactoryKeys,
|
|
2130
4361
|
"no-file-level-helpers": noFileLevelHelpers,
|
|
2131
4362
|
"no-imported-type-alias": noImportedTypeAlias,
|
|
2132
4363
|
"no-inline-schema-elements": noInlineSchemaElements,
|
|
4364
|
+
"no-inline-types": noInlineTypes,
|
|
4365
|
+
"no-literal-in": noLiteralIn,
|
|
2133
4366
|
"no-local-schema-construction": noLocalSchemaConstruction,
|
|
4367
|
+
"no-member-comments": noMemberComments,
|
|
4368
|
+
"no-module-mutable-state": noModuleMutableState,
|
|
4369
|
+
"no-promise-all-mutation": noPromiseAllMutation,
|
|
2134
4370
|
"no-rederive-schema": noRederiveSchema,
|
|
4371
|
+
"no-restricted-constructor": noRestrictedConstructor,
|
|
2135
4372
|
"no-restricted-files": noRestrictedFiles,
|
|
2136
4373
|
"no-restricted-token": noRestrictedToken,
|
|
2137
4374
|
"no-runtime-in-types": noRuntimeInTypes,
|
|
@@ -2140,12 +4377,16 @@ var src_default = eslintCompatPlugin({
|
|
|
2140
4377
|
"no-trivial-functions": noTrivialFunctions,
|
|
2141
4378
|
"no-type-declarations": noTypeDeclarations,
|
|
2142
4379
|
"no-unescaped-like": noUnescapedLike,
|
|
4380
|
+
"object-multiline": objectMultiline,
|
|
2143
4381
|
"only-export-components": onlyExportComponents,
|
|
4382
|
+
"only-export-constants": onlyExportConstants,
|
|
4383
|
+
"padding-between-statements": paddingBetweenStatements,
|
|
2144
4384
|
"require-file-factory": requireFileFactory,
|
|
2145
4385
|
"require-object-params": requireObjectParams,
|
|
2146
4386
|
"require-orpc-output": requireOrpcOutput,
|
|
2147
4387
|
"require-paired-call": requirePairedCall,
|
|
2148
|
-
"route-surface": routeSurface
|
|
4388
|
+
"route-surface": routeSurface,
|
|
4389
|
+
"test-title-pattern": testTitlePattern
|
|
2149
4390
|
}
|
|
2150
4391
|
});
|
|
2151
4392
|
export {
|