oxlint-plugin-arch 0.2.5 → 0.3.1
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 +17 -0
- package/README.md +241 -102
- package/dist/index.js +2214 -269
- 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;
|
|
@@ -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
1108
|
const stem = namingFileStem(basename, options.stem, options.trailingRoles, options.roleSeparators);
|
|
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: options.pattern ?? "" } : { name: item.name, prefix: prefixes.join(" or ") }
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
}
|
|
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: [{
|
|
@@ -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,127 +1371,554 @@ 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 options = optionsFirst(context);
|
|
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
|
+
const tokens = source.getTokensBetween(node.key, node.value);
|
|
1481
|
+
const valueStart = tokens[tokens.findIndex((token) => token.value === ":") + 1] ?? node.value;
|
|
1482
|
+
if (keyEndLine === valueStart.loc.start.line) {
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
const between = source.text.slice(keyEnd, node.value.range[0]);
|
|
1486
|
+
const commentsBetween = source.commentsExistBetween(node.key, node.value);
|
|
1487
|
+
const onlyColonAndSpace = /^[\s:]*$/.test(between) && between.includes(":");
|
|
1488
|
+
context.report({
|
|
1489
|
+
node,
|
|
1490
|
+
messageId: "sameLine",
|
|
1491
|
+
fix(fixer) {
|
|
1492
|
+
return !commentsBetween && onlyColonAndSpace ? fixer.replaceTextRange([keyEnd, node.value.range[0]], ": ") : null;
|
|
1493
|
+
}
|
|
1494
|
+
});
|
|
1110
1495
|
}
|
|
1111
1496
|
};
|
|
1112
1497
|
}
|
|
1113
1498
|
});
|
|
1114
1499
|
|
|
1115
|
-
// src/rules/no-
|
|
1116
|
-
import { defineRule as
|
|
1117
|
-
var
|
|
1500
|
+
// src/rules/no-comments.ts
|
|
1501
|
+
import { defineRule as defineRule11 } from "@oxlint/plugins";
|
|
1502
|
+
var NO_COMMENTS_LINE_GAP = /^[\t \r\n\u2028\u2029]*$/;
|
|
1503
|
+
var NO_COMMENTS_LINE_TERMINATOR = /\r\n|[\n\r\u2028\u2029]/;
|
|
1504
|
+
var NO_COMMENTS_TRAILING_LINES = /^[\t ]*(?:\r\n|[\n\r\u2028\u2029])(?:[\t ]*(?:\r\n|[\n\r\u2028\u2029]))*/;
|
|
1505
|
+
var NO_COMMENTS_HORIZONTAL_SPACE = /^[\t ]*$/;
|
|
1506
|
+
function noCommentsCreateLineIndex(text) {
|
|
1507
|
+
const starts = [0];
|
|
1508
|
+
const ends = [];
|
|
1509
|
+
for (let index = 0;index < text.length; index++) {
|
|
1510
|
+
const character = text[index];
|
|
1511
|
+
if (character === "\r") {
|
|
1512
|
+
ends.push(index);
|
|
1513
|
+
if (text[index + 1] === `
|
|
1514
|
+
`)
|
|
1515
|
+
index++;
|
|
1516
|
+
starts.push(index + 1);
|
|
1517
|
+
} else if (character === `
|
|
1518
|
+
` || character === "\u2028" || character === "\u2029") {
|
|
1519
|
+
ends.push(index);
|
|
1520
|
+
starts.push(index + 1);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
ends.push(text.length);
|
|
1524
|
+
return { starts, ends };
|
|
1525
|
+
}
|
|
1526
|
+
function noCommentsLineNumber(lines, offset) {
|
|
1527
|
+
let low = 0;
|
|
1528
|
+
let high = lines.starts.length;
|
|
1529
|
+
while (low < high) {
|
|
1530
|
+
const middle = low + high >>> 1;
|
|
1531
|
+
if (lines.starts[middle] <= offset)
|
|
1532
|
+
low = middle + 1;
|
|
1533
|
+
else
|
|
1534
|
+
high = middle;
|
|
1535
|
+
}
|
|
1536
|
+
return low - 1;
|
|
1537
|
+
}
|
|
1538
|
+
function noCommentsLineStart(lines, offset) {
|
|
1539
|
+
return lines.starts[noCommentsLineNumber(lines, offset)];
|
|
1540
|
+
}
|
|
1541
|
+
function noCommentsLineEnd(lines, offset) {
|
|
1542
|
+
return lines.ends[noCommentsLineNumber(lines, offset)];
|
|
1543
|
+
}
|
|
1544
|
+
function noCommentsIncludesLine(lines, start, end) {
|
|
1545
|
+
let low = 0;
|
|
1546
|
+
let high = lines.length;
|
|
1547
|
+
while (low < high) {
|
|
1548
|
+
const middle = low + high >>> 1;
|
|
1549
|
+
if (lines[middle] < start)
|
|
1550
|
+
low = middle + 1;
|
|
1551
|
+
else
|
|
1552
|
+
high = middle;
|
|
1553
|
+
}
|
|
1554
|
+
return lines[low] <= end;
|
|
1555
|
+
}
|
|
1556
|
+
function noCommentsFollowsOpeningBrace(text, offset) {
|
|
1557
|
+
for (let index = offset - 1;index >= 0; index--) {
|
|
1558
|
+
const character = text[index];
|
|
1559
|
+
if (!/\s/.test(character))
|
|
1560
|
+
return character === "{";
|
|
1561
|
+
}
|
|
1562
|
+
return false;
|
|
1563
|
+
}
|
|
1564
|
+
function noCommentsGroups(comments, text) {
|
|
1565
|
+
const groups = [];
|
|
1566
|
+
for (const comment of comments) {
|
|
1567
|
+
const current = groups.at(-1);
|
|
1568
|
+
const previous = current?.at(-1);
|
|
1569
|
+
if (current && previous) {
|
|
1570
|
+
const between = text.slice(previous.range[1], comment.range[0]);
|
|
1571
|
+
if (NO_COMMENTS_LINE_GAP.test(between)) {
|
|
1572
|
+
current.push(comment);
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
groups.push([comment]);
|
|
1577
|
+
}
|
|
1578
|
+
return groups;
|
|
1579
|
+
}
|
|
1580
|
+
function noCommentsJsxEmptyContainer(containers, start, end) {
|
|
1581
|
+
let low = 0;
|
|
1582
|
+
let high = containers.length;
|
|
1583
|
+
while (low < high) {
|
|
1584
|
+
const middle = low + high >>> 1;
|
|
1585
|
+
if (containers[middle].range[0] <= start)
|
|
1586
|
+
low = middle + 1;
|
|
1587
|
+
else
|
|
1588
|
+
high = middle;
|
|
1589
|
+
}
|
|
1590
|
+
const container = containers[low - 1];
|
|
1591
|
+
return container?.range[1] >= end ? container : undefined;
|
|
1592
|
+
}
|
|
1593
|
+
var noComments = defineRule11({
|
|
1118
1594
|
meta: {
|
|
1119
|
-
type: "
|
|
1595
|
+
type: "suggestion",
|
|
1596
|
+
fixable: "code",
|
|
1120
1597
|
schema: [{
|
|
1121
1598
|
type: "object",
|
|
1122
1599
|
additionalProperties: false,
|
|
1123
1600
|
properties: {
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1601
|
+
allowWhy: { type: "boolean", default: true },
|
|
1602
|
+
allowPatterns: { type: "array", items: { type: "string" } },
|
|
1603
|
+
allowJsdoc: { type: "boolean", default: false }
|
|
1127
1604
|
}
|
|
1128
1605
|
}],
|
|
1129
1606
|
messages: {
|
|
1130
|
-
|
|
1607
|
+
comment: "Remove this comment."
|
|
1131
1608
|
}
|
|
1132
1609
|
},
|
|
1133
1610
|
createOnce(context) {
|
|
1611
|
+
let allowWhy = true;
|
|
1612
|
+
let allowJsdoc = false;
|
|
1613
|
+
let allowed = [];
|
|
1134
1614
|
return {
|
|
1615
|
+
before() {
|
|
1616
|
+
const options = optionsFirst(context, {});
|
|
1617
|
+
allowWhy = options.allowWhy ?? true;
|
|
1618
|
+
allowJsdoc = options.allowJsdoc ?? false;
|
|
1619
|
+
allowed = (options.allowPatterns ?? []).map((pattern) => new RegExp(pattern));
|
|
1620
|
+
},
|
|
1135
1621
|
Program(program) {
|
|
1136
|
-
const
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1622
|
+
const source = context.sourceCode;
|
|
1623
|
+
const text = source.text;
|
|
1624
|
+
const comments = source.getAllComments().filter((comment) => comment.type !== "Shebang");
|
|
1625
|
+
const removable = [];
|
|
1626
|
+
const protectedTargetLines = [];
|
|
1627
|
+
let mightContainJsxComment = false;
|
|
1628
|
+
let whyContinuation = false;
|
|
1629
|
+
let previousComment;
|
|
1630
|
+
for (const comment of comments) {
|
|
1631
|
+
const value = commentsNormalizedValue(comment);
|
|
1632
|
+
if (!mightContainJsxComment && comment.type === "Block" && noCommentsFollowsOpeningBrace(text, comment.range[0])) {
|
|
1633
|
+
mightContainJsxComment = true;
|
|
1146
1634
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1635
|
+
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]));
|
|
1636
|
+
const continuesWhy = comment.type === "Line" && whyContinuation && directlyFollowsPrevious;
|
|
1637
|
+
const isWhy = commentsIsWhy(value) || continuesWhy;
|
|
1638
|
+
if (commentsIsDisableNextLine(value)) {
|
|
1639
|
+
protectedTargetLines.push(comment.loc.end.line + 1);
|
|
1640
|
+
}
|
|
1641
|
+
whyContinuation = comment.type === "Line" && isWhy;
|
|
1642
|
+
previousComment = comment;
|
|
1643
|
+
if (allowWhy && isWhy)
|
|
1644
|
+
continue;
|
|
1645
|
+
if (commentsIsDirective(value))
|
|
1646
|
+
continue;
|
|
1647
|
+
if (allowJsdoc && commentsIsJsdoc(text, comment))
|
|
1648
|
+
continue;
|
|
1649
|
+
if (allowed.some((pattern) => pattern.test(value.trim())))
|
|
1650
|
+
continue;
|
|
1651
|
+
removable.push(comment);
|
|
1652
|
+
}
|
|
1653
|
+
if (removable.length === 0)
|
|
1654
|
+
return;
|
|
1655
|
+
const lines = noCommentsCreateLineIndex(text);
|
|
1656
|
+
const jsxContainers = [];
|
|
1657
|
+
if (mightContainJsxComment) {
|
|
1658
|
+
astVisit(program, [], (node) => {
|
|
1659
|
+
if (node.type === "JSXExpressionContainer" && node.expression.type === "JSXEmptyExpression") {
|
|
1660
|
+
jsxContainers.push(node);
|
|
1661
|
+
}
|
|
1662
|
+
});
|
|
1663
|
+
jsxContainers.sort((left, right) => left.range[0] - right.range[0]);
|
|
1664
|
+
}
|
|
1665
|
+
for (const group of noCommentsGroups(removable, text)) {
|
|
1666
|
+
const first = group[0];
|
|
1667
|
+
const final = group[group.length - 1];
|
|
1668
|
+
const container = noCommentsJsxEmptyContainer(jsxContainers, first.range[0], final.range[1]);
|
|
1669
|
+
let embeddedUnicodeLineTerminator;
|
|
1670
|
+
for (const comment of group) {
|
|
1671
|
+
if (comment.type === "Block" && embeddedUnicodeLineTerminator === undefined) {
|
|
1672
|
+
embeddedUnicodeLineTerminator = text.slice(comment.range[0], comment.range[1]).match(/[\u2028\u2029]/)?.[0];
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
const reportOnly = container !== undefined || noCommentsIncludesLine(protectedTargetLines, first.loc.start.line, final.loc.end.line);
|
|
1676
|
+
const range0 = first.range[0];
|
|
1677
|
+
const range1 = final.range[1];
|
|
1678
|
+
context.report({
|
|
1679
|
+
loc: { start: first.loc.start, end: final.loc.end },
|
|
1680
|
+
messageId: "comment",
|
|
1681
|
+
...reportOnly ? {} : {
|
|
1682
|
+
fix(fixer) {
|
|
1683
|
+
const lineStart = noCommentsLineStart(lines, range0);
|
|
1684
|
+
const before = text.slice(lineStart, range0);
|
|
1685
|
+
if (NO_COMMENTS_HORIZONTAL_SPACE.test(before)) {
|
|
1686
|
+
const trailingLines = text.slice(range1).match(NO_COMMENTS_TRAILING_LINES);
|
|
1687
|
+
if (trailingLines) {
|
|
1688
|
+
const end = range1 + trailingLines[0].length;
|
|
1689
|
+
return embeddedUnicodeLineTerminator ? fixer.replaceTextRange([lineStart, end], embeddedUnicodeLineTerminator) : fixer.removeRange([lineStart, end]);
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
const removed = text.slice(range0, range1);
|
|
1693
|
+
const lineBreak = removed.match(NO_COMMENTS_LINE_TERMINATOR)?.[0];
|
|
1694
|
+
let afterOnLine;
|
|
1695
|
+
if (lineBreak) {
|
|
1696
|
+
afterOnLine = text.slice(range1, noCommentsLineEnd(lines, range1));
|
|
1697
|
+
if (/\S/.test(before) && /\S/.test(afterOnLine)) {
|
|
1698
|
+
return fixer.replaceTextRange([range0, range1], lineBreak);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
if (embeddedUnicodeLineTerminator) {
|
|
1702
|
+
return fixer.replaceTextRange([range0, range1], embeddedUnicodeLineTerminator);
|
|
1703
|
+
}
|
|
1704
|
+
afterOnLine ??= text.slice(range1, noCommentsLineEnd(lines, range1));
|
|
1705
|
+
if (NO_COMMENTS_HORIZONTAL_SPACE.test(afterOnLine)) {
|
|
1706
|
+
const start = range0 - (/[\t ]*$/.exec(before)?.[0].length ?? 0);
|
|
1707
|
+
return fixer.removeRange([start, range1]);
|
|
1708
|
+
}
|
|
1709
|
+
const left = text[range0 - 1];
|
|
1710
|
+
const right = text[range1];
|
|
1711
|
+
const separator = left && right && !/\s/.test(left) && !/\s/.test(right) ? " " : "";
|
|
1712
|
+
return fixer.replaceTextRange([range0, range1], separator);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1721
|
+
|
|
1722
|
+
// src/rules/no-extra-exports.ts
|
|
1723
|
+
import { defineRule as defineRule12 } from "@oxlint/plugins";
|
|
1724
|
+
var noExtraExports = defineRule12({
|
|
1725
|
+
meta: {
|
|
1726
|
+
type: "problem",
|
|
1727
|
+
schema: [{
|
|
1728
|
+
type: "object",
|
|
1729
|
+
additionalProperties: false,
|
|
1730
|
+
properties: {
|
|
1731
|
+
names: { type: "array", items: { type: "string" } },
|
|
1732
|
+
patterns: { type: "array", items: { type: "string" } },
|
|
1733
|
+
domainStem: { type: "string", enum: [...namingStemModes] },
|
|
1734
|
+
trailingRoles: { type: "array", items: { type: "string" } },
|
|
1735
|
+
roleSeparators: { type: "array", items: { type: "string" } },
|
|
1736
|
+
allowTypeExports: { type: "boolean" }
|
|
1737
|
+
},
|
|
1738
|
+
required: ["names"]
|
|
1739
|
+
}],
|
|
1740
|
+
messages: {
|
|
1741
|
+
extraExport: "Export '{{name}}' is not allowed; expected one of: {{allowed}}."
|
|
1742
|
+
}
|
|
1743
|
+
},
|
|
1744
|
+
createOnce(context) {
|
|
1745
|
+
return {
|
|
1746
|
+
Program(program) {
|
|
1747
|
+
const options = optionsFirst(context);
|
|
1748
|
+
const basename = namingFileBasename(context.filename).replace(/\.(tsx?|jsx?)$/, "");
|
|
1749
|
+
const domain = namingFileStem(basename, options.domainStem, options.trailingRoles, options.roleSeparators);
|
|
1750
|
+
const replacements = {
|
|
1751
|
+
Domain: namingPascalCase(domain),
|
|
1752
|
+
domain: namingCamelCase(domain)
|
|
1753
|
+
};
|
|
1754
|
+
const allowed = options.names.map((template) => template.replaceAll("{Domain}", replacements.Domain).replaceAll("{domain}", replacements.domain));
|
|
1755
|
+
const patterns = (options.patterns ?? []).map((template) => {
|
|
1756
|
+
const source = template.replaceAll("{Domain}", replacements.Domain.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")).replaceAll("{domain}", replacements.domain.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
1757
|
+
return new RegExp(`^(?:${source})$`);
|
|
1758
|
+
});
|
|
1759
|
+
for (const binding of exportsCollect(program)) {
|
|
1760
|
+
if (options.allowTypeExports && binding.typeOnly) {
|
|
1761
|
+
continue;
|
|
1762
|
+
}
|
|
1763
|
+
const name = binding.localName ?? binding.exportedName;
|
|
1764
|
+
if (!allowed.includes(name) && !patterns.some((pattern) => pattern.test(name))) {
|
|
1765
|
+
context.report({
|
|
1766
|
+
node: binding.node,
|
|
1767
|
+
messageId: "extraExport",
|
|
1768
|
+
data: {
|
|
1769
|
+
name,
|
|
1770
|
+
allowed: [...allowed, ...patterns.map((pattern) => pattern.toString())].join(", ")
|
|
1771
|
+
}
|
|
1772
|
+
});
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
});
|
|
1779
|
+
|
|
1780
|
+
// src/rules/no-extra-factory-keys.ts
|
|
1781
|
+
import { defineRule as defineRule13 } from "@oxlint/plugins";
|
|
1782
|
+
function compileFactoryKeys(pattern, keys, requireKeys) {
|
|
1783
|
+
const allowedList = keys.concat(requireKeys.filter((key) => !keys.includes(key)));
|
|
1784
|
+
return {
|
|
1785
|
+
pattern: new RegExp(pattern),
|
|
1786
|
+
allowed: new Set(allowedList),
|
|
1787
|
+
allowedList,
|
|
1788
|
+
requireKeys
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
var noExtraFactoryKeys = defineRule13({
|
|
1792
|
+
meta: {
|
|
1793
|
+
type: "problem",
|
|
1794
|
+
schema: [{
|
|
1795
|
+
type: "object",
|
|
1796
|
+
additionalProperties: false,
|
|
1797
|
+
properties: {
|
|
1798
|
+
keys: { type: "array", items: { type: "string" } },
|
|
1799
|
+
factoryPattern: { type: "string" },
|
|
1800
|
+
requireKeys: { type: "array", items: { type: "string" } },
|
|
1801
|
+
keySets: {
|
|
1802
|
+
type: "array",
|
|
1803
|
+
items: {
|
|
1804
|
+
type: "object",
|
|
1805
|
+
additionalProperties: false,
|
|
1806
|
+
properties: {
|
|
1807
|
+
factoryPattern: { type: "string" },
|
|
1808
|
+
keys: { type: "array", items: { type: "string" } },
|
|
1809
|
+
requireKeys: { type: "array", items: { type: "string" } }
|
|
1810
|
+
},
|
|
1811
|
+
required: ["factoryPattern", "keys"]
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
}],
|
|
1816
|
+
messages: {
|
|
1817
|
+
extraKey: "Factory '{{factory}}' must not return key '{{key}}'; allowed keys: {{allowed}}.",
|
|
1818
|
+
missingKey: "Factory '{{factory}}' must return key '{{key}}'.",
|
|
1819
|
+
emptyObject: "Factory '{{factory}}' must not return an empty object."
|
|
1820
|
+
}
|
|
1821
|
+
},
|
|
1822
|
+
createOnce(context) {
|
|
1823
|
+
let keySets = [];
|
|
1824
|
+
let fallback;
|
|
1825
|
+
function resolveFactoryKeys(name) {
|
|
1826
|
+
const matched = keySets.find((item) => item.pattern.test(name));
|
|
1827
|
+
if (matched) {
|
|
1828
|
+
return matched;
|
|
1829
|
+
}
|
|
1830
|
+
if (fallback?.pattern.test(name)) {
|
|
1831
|
+
return fallback;
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
return {
|
|
1835
|
+
before() {
|
|
1836
|
+
const options = optionsFirst(context, {});
|
|
1837
|
+
const { keys, factoryPattern = "^make[A-Z]", requireKeys = [], keySets: configured } = options;
|
|
1838
|
+
if (!keys && !configured) {
|
|
1839
|
+
return false;
|
|
1840
|
+
}
|
|
1841
|
+
keySets = (configured ?? []).map((item) => compileFactoryKeys(item.factoryPattern, item.keys, item.requireKeys ?? []));
|
|
1842
|
+
fallback = keys ? compileFactoryKeys(factoryPattern, keys, requireKeys) : undefined;
|
|
1843
|
+
},
|
|
1844
|
+
Program(program) {
|
|
1845
|
+
for (const factory of factoriesCollectTopLevel(program, /^/)) {
|
|
1846
|
+
const resolved = resolveFactoryKeys(factory.name);
|
|
1847
|
+
if (!resolved) {
|
|
1848
|
+
continue;
|
|
1849
|
+
}
|
|
1850
|
+
for (const object of factoriesDirectlyReturnedObjects(factory)) {
|
|
1851
|
+
const present = new Set(object.properties.flatMap((property) => {
|
|
1852
|
+
const key = factoriesObjectPropertyName(property);
|
|
1853
|
+
return key ? [key] : [];
|
|
1854
|
+
}));
|
|
1855
|
+
if (object.properties.length === 0 && resolved.requireKeys.length > 0) {
|
|
1856
|
+
context.report({
|
|
1857
|
+
node: object,
|
|
1858
|
+
messageId: "emptyObject",
|
|
1859
|
+
data: { factory: factory.name }
|
|
1860
|
+
});
|
|
1861
|
+
}
|
|
1862
|
+
for (const property of object.properties) {
|
|
1863
|
+
const key = factoriesObjectPropertyName(property);
|
|
1864
|
+
if (!key || !resolved.allowed.has(key)) {
|
|
1865
|
+
context.report({
|
|
1866
|
+
node: property,
|
|
1867
|
+
messageId: "extraKey",
|
|
1868
|
+
data: { factory: factory.name, key: key ?? "<computed or spread>", allowed: resolved.allowedList.join(", ") }
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
for (const key of resolved.requireKeys) {
|
|
1873
|
+
if (!present.has(key)) {
|
|
1874
|
+
context.report({
|
|
1875
|
+
node: object,
|
|
1876
|
+
messageId: "missingKey",
|
|
1877
|
+
data: { factory: factory.name, key }
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1885
|
+
}
|
|
1886
|
+
});
|
|
1887
|
+
|
|
1888
|
+
// src/rules/no-file-level-helpers.ts
|
|
1889
|
+
import { defineRule as defineRule14 } from "@oxlint/plugins";
|
|
1890
|
+
var noFileLevelHelpers = defineRule14({
|
|
1891
|
+
meta: {
|
|
1892
|
+
type: "problem",
|
|
1893
|
+
schema: [{
|
|
1894
|
+
type: "object",
|
|
1895
|
+
additionalProperties: false,
|
|
1896
|
+
properties: {
|
|
1897
|
+
allowPattern: { type: "string" },
|
|
1898
|
+
detectComponents: { type: "boolean" },
|
|
1899
|
+
hookPattern: { type: "string" }
|
|
1900
|
+
}
|
|
1901
|
+
}],
|
|
1902
|
+
messages: {
|
|
1903
|
+
helper: "File-level helper {{name}} must move inside its owning component or hook."
|
|
1904
|
+
}
|
|
1905
|
+
},
|
|
1906
|
+
createOnce(context) {
|
|
1907
|
+
return {
|
|
1908
|
+
Program(program) {
|
|
1909
|
+
const {
|
|
1910
|
+
allowPattern,
|
|
1911
|
+
detectComponents = true,
|
|
1912
|
+
hookPattern = "^use[A-Z]"
|
|
1913
|
+
} = optionsFirst(context, {});
|
|
1914
|
+
const allowed = allowPattern ? new RegExp(allowPattern) : null;
|
|
1915
|
+
const hooks = new RegExp(hookPattern);
|
|
1916
|
+
for (const candidate of declarationsFileLevelFunctionCandidates(program)) {
|
|
1917
|
+
if (detectComponents && reactComponentsIsLike(program, candidate) || hooks.test(candidate.name) || allowed?.test(candidate.name)) {
|
|
1918
|
+
continue;
|
|
1919
|
+
}
|
|
1920
|
+
context.report({
|
|
1921
|
+
node: candidate.node,
|
|
1149
1922
|
messageId: "helper",
|
|
1150
1923
|
data: { name: candidate.name }
|
|
1151
1924
|
});
|
|
@@ -1156,7 +1929,7 @@ var noFileLevelHelpers = defineRule9({
|
|
|
1156
1929
|
});
|
|
1157
1930
|
|
|
1158
1931
|
// src/rules/no-imported-type-alias.ts
|
|
1159
|
-
import { defineRule as
|
|
1932
|
+
import { defineRule as defineRule15 } from "@oxlint/plugins";
|
|
1160
1933
|
function importedAliasTarget(declaration, imports) {
|
|
1161
1934
|
if (declaration?.type !== "TSTypeAliasDeclaration" || declaration.typeAnnotation.type !== "TSTypeReference" || declaration.typeAnnotation.typeName.type !== "Identifier" || !imports.has(declaration.typeAnnotation.typeName.name)) {
|
|
1162
1935
|
return;
|
|
@@ -1166,7 +1939,7 @@ function importedAliasTarget(declaration, imports) {
|
|
|
1166
1939
|
function specifierLocalName(specifier) {
|
|
1167
1940
|
return specifier.local.type === "Identifier" ? specifier.local.name : specifier.local.value;
|
|
1168
1941
|
}
|
|
1169
|
-
var noImportedTypeAlias =
|
|
1942
|
+
var noImportedTypeAlias = defineRule15({
|
|
1170
1943
|
meta: {
|
|
1171
1944
|
type: "problem",
|
|
1172
1945
|
schema: [],
|
|
@@ -1216,8 +1989,8 @@ var noImportedTypeAlias = defineRule10({
|
|
|
1216
1989
|
});
|
|
1217
1990
|
|
|
1218
1991
|
// src/rules/no-inline-schema-elements.ts
|
|
1219
|
-
import { defineRule as
|
|
1220
|
-
var noInlineSchemaElements =
|
|
1992
|
+
import { defineRule as defineRule16 } from "@oxlint/plugins";
|
|
1993
|
+
var noInlineSchemaElements = defineRule16({
|
|
1221
1994
|
meta: {
|
|
1222
1995
|
type: "problem",
|
|
1223
1996
|
schema: [{
|
|
@@ -1265,7 +2038,7 @@ var noInlineSchemaElements = defineRule11({
|
|
|
1265
2038
|
});
|
|
1266
2039
|
|
|
1267
2040
|
// src/rules/no-inline-types.ts
|
|
1268
|
-
import { defineRule as
|
|
2041
|
+
import { defineRule as defineRule17 } from "@oxlint/plugins";
|
|
1269
2042
|
function parameterAnnotation(parameter) {
|
|
1270
2043
|
if (parameter.type === "TSParameterProperty") {
|
|
1271
2044
|
return parameterAnnotation(parameter.parameter);
|
|
@@ -1275,7 +2048,7 @@ function parameterAnnotation(parameter) {
|
|
|
1275
2048
|
}
|
|
1276
2049
|
return parameter.typeAnnotation;
|
|
1277
2050
|
}
|
|
1278
|
-
var noInlineTypes =
|
|
2051
|
+
var noInlineTypes = defineRule17({
|
|
1279
2052
|
meta: {
|
|
1280
2053
|
type: "problem",
|
|
1281
2054
|
schema: [{
|
|
@@ -1284,7 +2057,8 @@ var noInlineTypes = defineRule12({
|
|
|
1284
2057
|
properties: {
|
|
1285
2058
|
parameters: { type: "boolean" },
|
|
1286
2059
|
returns: { type: "boolean" },
|
|
1287
|
-
functionTypes: { type: "boolean" }
|
|
2060
|
+
functionTypes: { type: "boolean" },
|
|
2061
|
+
minMembers: { type: "integer", minimum: 1 }
|
|
1288
2062
|
}
|
|
1289
2063
|
}],
|
|
1290
2064
|
messages: {
|
|
@@ -1292,48 +2066,111 @@ var noInlineTypes = defineRule12({
|
|
|
1292
2066
|
}
|
|
1293
2067
|
},
|
|
1294
2068
|
createOnce(context) {
|
|
2069
|
+
let checkParameters = true;
|
|
2070
|
+
let checkReturns = true;
|
|
2071
|
+
let checkFunctionTypes = true;
|
|
2072
|
+
let minMembers = 1;
|
|
2073
|
+
function isInline(node) {
|
|
2074
|
+
return node.type === "TSTypeLiteral" && (minMembers === 1 || node.members.length >= minMembers) || checkFunctionTypes && (node.type === "TSFunctionType" || node.type === "TSConstructorType");
|
|
2075
|
+
}
|
|
2076
|
+
function check(annotation, position) {
|
|
2077
|
+
astVisit(annotation, [], (node, ancestors) => {
|
|
2078
|
+
if (!isInline(node) || ancestors.some(isInline)) {
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
context.report({
|
|
2082
|
+
node,
|
|
2083
|
+
messageId: "inlineType",
|
|
2084
|
+
data: { kind: node.type === "TSTypeLiteral" ? "object" : "function", position }
|
|
2085
|
+
});
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
function checkFunction(node) {
|
|
2089
|
+
if (checkParameters) {
|
|
2090
|
+
for (const parameter of node.params) {
|
|
2091
|
+
check(parameterAnnotation(parameter), "parameter");
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
if (checkReturns) {
|
|
2095
|
+
check(node.returnType, "return");
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
1295
2098
|
return {
|
|
1296
|
-
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
2099
|
+
before() {
|
|
2100
|
+
const options = optionsFirst(context, {});
|
|
2101
|
+
checkParameters = options.parameters ?? true;
|
|
2102
|
+
checkReturns = options.returns ?? true;
|
|
2103
|
+
checkFunctionTypes = options.functionTypes ?? true;
|
|
2104
|
+
minMembers = options.minMembers ?? 1;
|
|
2105
|
+
if (!checkParameters && !checkReturns) {
|
|
2106
|
+
return false;
|
|
2107
|
+
}
|
|
2108
|
+
},
|
|
2109
|
+
FunctionDeclaration: checkFunction,
|
|
2110
|
+
FunctionExpression: checkFunction,
|
|
2111
|
+
ArrowFunctionExpression: checkFunction,
|
|
2112
|
+
TSDeclareFunction: checkFunction,
|
|
2113
|
+
TSEmptyBodyFunctionExpression: checkFunction
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
});
|
|
2117
|
+
|
|
2118
|
+
// src/rules/no-literal-in.ts
|
|
2119
|
+
import { defineRule as defineRule18 } from "@oxlint/plugins";
|
|
2120
|
+
function literalKey(node) {
|
|
2121
|
+
if (node.type === "Literal" && typeof node.value === "string") {
|
|
2122
|
+
return node.value;
|
|
2123
|
+
}
|
|
2124
|
+
if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
|
|
2125
|
+
return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
var noLiteralIn = defineRule18({
|
|
2129
|
+
meta: {
|
|
2130
|
+
type: "suggestion",
|
|
2131
|
+
schema: [{
|
|
2132
|
+
type: "object",
|
|
2133
|
+
additionalProperties: false,
|
|
2134
|
+
properties: {
|
|
2135
|
+
allow: { type: "array", items: { type: "string" } }
|
|
2136
|
+
}
|
|
2137
|
+
}],
|
|
2138
|
+
defaultOptions: [{ allow: [] }],
|
|
2139
|
+
messages: {
|
|
2140
|
+
literalIn: "Avoid '{{key}}' in value; use Object.hasOwn() or a discriminated union."
|
|
2141
|
+
}
|
|
2142
|
+
},
|
|
2143
|
+
createOnce(context) {
|
|
2144
|
+
let allow = new Set;
|
|
2145
|
+
return {
|
|
2146
|
+
before() {
|
|
2147
|
+
allow = new Set(optionsFirst(context, { allow: [] }).allow);
|
|
2148
|
+
},
|
|
2149
|
+
BinaryExpression(node) {
|
|
2150
|
+
if (node.operator !== "in") {
|
|
2151
|
+
return;
|
|
2152
|
+
}
|
|
2153
|
+
const key = literalKey(node.left);
|
|
2154
|
+
if (key !== undefined && !allow.has(key)) {
|
|
2155
|
+
context.report({
|
|
2156
|
+
node: node.left,
|
|
2157
|
+
messageId: "literalIn",
|
|
2158
|
+
data: { key }
|
|
1309
2159
|
});
|
|
1310
2160
|
}
|
|
1311
|
-
astVisit(program, [], (node) => {
|
|
1312
|
-
if (node.type !== "FunctionDeclaration" && node.type !== "FunctionExpression" && node.type !== "ArrowFunctionExpression" && node.type !== "TSDeclareFunction" && node.type !== "TSEmptyBodyFunctionExpression") {
|
|
1313
|
-
return;
|
|
1314
|
-
}
|
|
1315
|
-
if (parameters) {
|
|
1316
|
-
for (const parameter of node.params) {
|
|
1317
|
-
check(parameterAnnotation(parameter), "parameter");
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
if (returns) {
|
|
1321
|
-
check(node.returnType, "return");
|
|
1322
|
-
}
|
|
1323
|
-
});
|
|
1324
2161
|
}
|
|
1325
2162
|
};
|
|
1326
2163
|
}
|
|
1327
2164
|
});
|
|
1328
2165
|
|
|
1329
2166
|
// src/rules/no-local-schema-construction.ts
|
|
1330
|
-
import { defineRule as
|
|
2167
|
+
import { defineRule as defineRule19 } from "@oxlint/plugins";
|
|
1331
2168
|
var defaultRuntimeImportMessage = "Do not import runtime schema constructors from {{source}}; import a named schema instead.";
|
|
1332
2169
|
var defaultConstructionMessage = "Do not construct schemas locally; import a named schema instead.";
|
|
1333
2170
|
function formatMessage(template, data) {
|
|
1334
2171
|
return Object.entries(data).reduce((result, [key, value]) => result.replaceAll(`{{${key}}}`, value), template);
|
|
1335
2172
|
}
|
|
1336
|
-
var noLocalSchemaConstruction =
|
|
2173
|
+
var noLocalSchemaConstruction = defineRule19({
|
|
1337
2174
|
meta: {
|
|
1338
2175
|
type: "problem",
|
|
1339
2176
|
schema: [{
|
|
@@ -1445,9 +2282,8 @@ var noLocalSchemaConstruction = defineRule13({
|
|
|
1445
2282
|
});
|
|
1446
2283
|
|
|
1447
2284
|
// src/rules/no-member-comments.ts
|
|
1448
|
-
import { defineRule as
|
|
1449
|
-
var
|
|
1450
|
-
var noMemberComments = defineRule14({
|
|
2285
|
+
import { defineRule as defineRule20 } from "@oxlint/plugins";
|
|
2286
|
+
var noMemberComments = defineRule20({
|
|
1451
2287
|
meta: {
|
|
1452
2288
|
type: "suggestion",
|
|
1453
2289
|
fixable: "code",
|
|
@@ -1479,8 +2315,8 @@ var noMemberComments = defineRule14({
|
|
|
1479
2315
|
let previousLine = -1;
|
|
1480
2316
|
const removable = [];
|
|
1481
2317
|
for (const comment of comments) {
|
|
1482
|
-
const value = (comment
|
|
1483
|
-
const isWhy = value
|
|
2318
|
+
const value = commentsNormalizedValue(comment);
|
|
2319
|
+
const isWhy = commentsIsWhy(value) || comment.type === "Line" && whyContinuation && comment.loc.start.line === previousLine + 1;
|
|
1484
2320
|
whyContinuation = comment.type === "Line" && isWhy;
|
|
1485
2321
|
previousLine = comment.loc.end.line;
|
|
1486
2322
|
if (reported.has(comment.range[0]))
|
|
@@ -1488,7 +2324,7 @@ var noMemberComments = defineRule14({
|
|
|
1488
2324
|
reported.add(comment.range[0]);
|
|
1489
2325
|
if (allowWhy && isWhy)
|
|
1490
2326
|
continue;
|
|
1491
|
-
if (
|
|
2327
|
+
if (commentsIsDirective(value))
|
|
1492
2328
|
continue;
|
|
1493
2329
|
removable.push(comment);
|
|
1494
2330
|
}
|
|
@@ -1540,36 +2376,249 @@ var noMemberComments = defineRule14({
|
|
|
1540
2376
|
}
|
|
1541
2377
|
});
|
|
1542
2378
|
|
|
1543
|
-
// src/rules/no-
|
|
1544
|
-
import { defineRule as
|
|
1545
|
-
|
|
2379
|
+
// src/rules/no-module-mutable-state.ts
|
|
2380
|
+
import { defineRule as defineRule21 } from "@oxlint/plugins";
|
|
2381
|
+
function moduleMutableStateBindings(pattern) {
|
|
2382
|
+
if (pattern.type === "Identifier") {
|
|
2383
|
+
return [{ name: pattern.name, node: pattern }];
|
|
2384
|
+
}
|
|
2385
|
+
if (pattern.type === "AssignmentPattern") {
|
|
2386
|
+
return moduleMutableStateBindings(pattern.left);
|
|
2387
|
+
}
|
|
2388
|
+
if (pattern.type === "RestElement") {
|
|
2389
|
+
return moduleMutableStateBindings(pattern.argument);
|
|
2390
|
+
}
|
|
2391
|
+
if (pattern.type === "ObjectPattern") {
|
|
2392
|
+
return pattern.properties.flatMap((property) => property.type === "RestElement" ? moduleMutableStateBindings(property) : moduleMutableStateBindings(property.value));
|
|
2393
|
+
}
|
|
2394
|
+
if (pattern.type === "ArrayPattern") {
|
|
2395
|
+
return pattern.elements.flatMap((element) => element ? moduleMutableStateBindings(element) : []);
|
|
2396
|
+
}
|
|
2397
|
+
return [];
|
|
2398
|
+
}
|
|
2399
|
+
function moduleMutableStateInsideFunction(node) {
|
|
2400
|
+
for (let current = node.parent;current; current = current.parent) {
|
|
2401
|
+
if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression" || current.type === "TSDeclareFunction" || current.type === "TSEmptyBodyFunctionExpression" || current.type === "StaticBlock") {
|
|
2402
|
+
return true;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
return false;
|
|
2406
|
+
}
|
|
2407
|
+
function moduleMutableStateIsModuleLet(node) {
|
|
2408
|
+
const parent = node.parent;
|
|
2409
|
+
return parent?.type === "Program" || parent?.type === "ExportNamedDeclaration" && parent.parent?.type === "Program";
|
|
2410
|
+
}
|
|
2411
|
+
function moduleMutableStateInsideAmbient(node) {
|
|
2412
|
+
for (let current = node.parent;current; current = current.parent) {
|
|
2413
|
+
if (current.type === "TSModuleDeclaration" && (current.declare || current.global || current.id.type === "Literal")) {
|
|
2414
|
+
return true;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
return false;
|
|
2418
|
+
}
|
|
2419
|
+
var noModuleMutableState = defineRule21({
|
|
1546
2420
|
meta: {
|
|
1547
2421
|
type: "problem",
|
|
1548
2422
|
schema: [{
|
|
1549
2423
|
type: "object",
|
|
1550
2424
|
additionalProperties: false,
|
|
1551
2425
|
properties: {
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
2426
|
+
kinds: {
|
|
2427
|
+
type: "array",
|
|
2428
|
+
items: { type: "string", enum: ["let", "var"] }
|
|
2429
|
+
},
|
|
2430
|
+
allowNamePattern: { type: "string" }
|
|
2431
|
+
}
|
|
1557
2432
|
}],
|
|
1558
2433
|
messages: {
|
|
1559
|
-
|
|
2434
|
+
moduleState: "module state is shared across requests/tests; keep it local."
|
|
1560
2435
|
}
|
|
1561
2436
|
},
|
|
1562
2437
|
createOnce(context) {
|
|
1563
|
-
let
|
|
2438
|
+
let kindSet = new Set(["let", "var"]);
|
|
2439
|
+
let allowed = null;
|
|
1564
2440
|
return {
|
|
1565
|
-
|
|
1566
|
-
const {
|
|
1567
|
-
|
|
2441
|
+
before() {
|
|
2442
|
+
const { kinds = ["let", "var"], allowNamePattern } = optionsFirst(context, {});
|
|
2443
|
+
kindSet = new Set(kinds);
|
|
2444
|
+
allowed = allowNamePattern ? new RegExp(allowNamePattern) : null;
|
|
1568
2445
|
},
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
2446
|
+
VariableDeclaration(node) {
|
|
2447
|
+
if (node.declare || !kindSet.has(node.kind)) {
|
|
2448
|
+
return;
|
|
2449
|
+
}
|
|
2450
|
+
if (node.kind === "let" && !moduleMutableStateIsModuleLet(node) || node.kind === "var" && moduleMutableStateInsideFunction(node) || moduleMutableStateInsideAmbient(node)) {
|
|
2451
|
+
return;
|
|
2452
|
+
}
|
|
2453
|
+
for (const declarator of node.declarations) {
|
|
2454
|
+
for (const binding of moduleMutableStateBindings(declarator.id)) {
|
|
2455
|
+
if (!allowed?.test(binding.name)) {
|
|
2456
|
+
context.report({
|
|
2457
|
+
node: binding.node,
|
|
2458
|
+
messageId: "moduleState"
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
});
|
|
2467
|
+
|
|
2468
|
+
// src/rules/no-promise-all-mutation.ts
|
|
2469
|
+
import { defineRule as defineRule22 } from "@oxlint/plugins";
|
|
2470
|
+
function mutationBinding(node) {
|
|
2471
|
+
if (node.type === "Identifier") {
|
|
2472
|
+
return node;
|
|
2473
|
+
}
|
|
2474
|
+
if (node.type === "MemberExpression") {
|
|
2475
|
+
return mutationBinding(node.object);
|
|
2476
|
+
}
|
|
2477
|
+
if (node.type === "ChainExpression" || node.type === "ParenthesizedExpression" || node.type === "TSAsExpression" || node.type === "TSSatisfiesExpression" || node.type === "TSTypeAssertion" || node.type === "TSNonNullExpression") {
|
|
2478
|
+
return mutationBinding(node.expression);
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
function mutationBindings(node) {
|
|
2482
|
+
switch (node.type) {
|
|
2483
|
+
case "ArrayPattern":
|
|
2484
|
+
return node.elements.flatMap((element) => element ? mutationBindings(element) : []);
|
|
2485
|
+
case "ObjectPattern":
|
|
2486
|
+
return node.properties.flatMap((property) => property.type === "RestElement" ? mutationBindings(property) : mutationBindings(property.value));
|
|
2487
|
+
case "AssignmentPattern":
|
|
2488
|
+
return mutationBindings(node.left);
|
|
2489
|
+
case "RestElement":
|
|
2490
|
+
return mutationBindings(node.argument);
|
|
2491
|
+
default: {
|
|
2492
|
+
const binding = mutationBinding(node);
|
|
2493
|
+
return binding ? [binding] : [];
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
var noPromiseAllMutation = defineRule22({
|
|
2498
|
+
meta: {
|
|
2499
|
+
type: "problem",
|
|
2500
|
+
schema: [{
|
|
2501
|
+
type: "object",
|
|
2502
|
+
additionalProperties: false,
|
|
2503
|
+
properties: {
|
|
2504
|
+
combinators: { type: "array", items: { type: "string" } },
|
|
2505
|
+
methods: { type: "array", items: { type: "string" } },
|
|
2506
|
+
checkAssignments: { type: "boolean" }
|
|
2507
|
+
}
|
|
2508
|
+
}],
|
|
2509
|
+
defaultOptions: [{
|
|
2510
|
+
combinators: ["Promise.all", "Promise.allSettled", "Promise.any", "Promise.race"],
|
|
2511
|
+
methods: ["push", "unshift", "splice", "set", "add"],
|
|
2512
|
+
checkAssignments: true
|
|
2513
|
+
}],
|
|
2514
|
+
messages: {
|
|
2515
|
+
mutation: "Do not mutate outer binding '{{name}}' inside {{combinator}}()."
|
|
2516
|
+
}
|
|
2517
|
+
},
|
|
2518
|
+
createOnce(context) {
|
|
2519
|
+
const references = new Map;
|
|
2520
|
+
const reported = new Set;
|
|
2521
|
+
let combinators = new Set;
|
|
2522
|
+
let methods = new Set;
|
|
2523
|
+
let checkAssignments = true;
|
|
2524
|
+
let referencesReady = false;
|
|
2525
|
+
function ensureReferences() {
|
|
2526
|
+
if (referencesReady) {
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
referencesReady = true;
|
|
2530
|
+
for (const scope of context.sourceCode.scopeManager.scopes) {
|
|
2531
|
+
for (const reference of scope.references) {
|
|
2532
|
+
references.set(reference.identifier.range[0], reference.resolved);
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
function isOuterBinding(identifier, call) {
|
|
2537
|
+
const variable = references.get(identifier.range[0]);
|
|
2538
|
+
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]);
|
|
2539
|
+
}
|
|
2540
|
+
function reportMutation(node, binding, call, combinator) {
|
|
2541
|
+
if (!binding || reported.has(binding.range[0]) || !isOuterBinding(binding, call)) {
|
|
2542
|
+
return;
|
|
2543
|
+
}
|
|
2544
|
+
reported.add(binding.range[0]);
|
|
2545
|
+
context.report({
|
|
2546
|
+
node,
|
|
2547
|
+
messageId: "mutation",
|
|
2548
|
+
data: { name: binding.name, combinator }
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
2551
|
+
return {
|
|
2552
|
+
before() {
|
|
2553
|
+
const options = optionsFirst(context);
|
|
2554
|
+
combinators = new Set(options.combinators);
|
|
2555
|
+
methods = new Set(options.methods);
|
|
2556
|
+
checkAssignments = options.checkAssignments;
|
|
2557
|
+
references.clear();
|
|
2558
|
+
reported.clear();
|
|
2559
|
+
referencesReady = false;
|
|
2560
|
+
},
|
|
2561
|
+
CallExpression(node) {
|
|
2562
|
+
const combinator = astDottedName(node.callee);
|
|
2563
|
+
if (!combinator || !combinators.has(combinator)) {
|
|
2564
|
+
return;
|
|
2565
|
+
}
|
|
2566
|
+
ensureReferences();
|
|
2567
|
+
for (const argument of node.arguments) {
|
|
2568
|
+
astVisit(argument, [node], (candidate) => {
|
|
2569
|
+
if (candidate.type === "CallExpression" && candidate.callee.type === "MemberExpression") {
|
|
2570
|
+
const method = astStaticMemberName(candidate.callee);
|
|
2571
|
+
if (method && methods.has(method)) {
|
|
2572
|
+
reportMutation(candidate, mutationBinding(candidate.callee.object), node, combinator);
|
|
2573
|
+
}
|
|
2574
|
+
return;
|
|
2575
|
+
}
|
|
2576
|
+
if (checkAssignments && candidate.type === "AssignmentExpression") {
|
|
2577
|
+
for (const binding of mutationBindings(candidate.left)) {
|
|
2578
|
+
reportMutation(candidate, binding, node, combinator);
|
|
2579
|
+
}
|
|
2580
|
+
return;
|
|
2581
|
+
}
|
|
2582
|
+
if (checkAssignments && candidate.type === "UpdateExpression") {
|
|
2583
|
+
reportMutation(candidate, mutationBinding(candidate.argument), node, combinator);
|
|
2584
|
+
}
|
|
2585
|
+
});
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
};
|
|
2589
|
+
}
|
|
2590
|
+
});
|
|
2591
|
+
|
|
2592
|
+
// src/rules/no-rederive-schema.ts
|
|
2593
|
+
import { defineRule as defineRule23 } from "@oxlint/plugins";
|
|
2594
|
+
var noRederiveSchema = defineRule23({
|
|
2595
|
+
meta: {
|
|
2596
|
+
type: "problem",
|
|
2597
|
+
schema: [{
|
|
2598
|
+
type: "object",
|
|
2599
|
+
additionalProperties: false,
|
|
2600
|
+
properties: {
|
|
2601
|
+
from: { type: "array", items: { type: "string" } },
|
|
2602
|
+
namespaces: { type: "array", items: { type: "string" } },
|
|
2603
|
+
operators: { type: "array", items: { type: "string" } }
|
|
2604
|
+
},
|
|
2605
|
+
required: ["from"]
|
|
2606
|
+
}],
|
|
2607
|
+
messages: {
|
|
2608
|
+
rederive: "Do not derive {{operator}} from imported schema {{schema}}; import its DTO type."
|
|
2609
|
+
}
|
|
2610
|
+
},
|
|
2611
|
+
createOnce(context) {
|
|
2612
|
+
let imported = new Set;
|
|
2613
|
+
return {
|
|
2614
|
+
Program(program) {
|
|
2615
|
+
const { from } = optionsFirst(context);
|
|
2616
|
+
imported = new Set(program.body.flatMap((statement) => statement.type === "ImportDeclaration" && from.includes(statement.source.value) ? statement.specifiers.map((specifier) => specifier.local.name) : []));
|
|
2617
|
+
},
|
|
2618
|
+
TSTypeReference(node) {
|
|
2619
|
+
const {
|
|
2620
|
+
namespaces = ["z"],
|
|
2621
|
+
operators = ["infer", "input"]
|
|
1573
2622
|
} = optionsFirst(context);
|
|
1574
2623
|
const operator = schemasTypeOperator(node, new Set(namespaces), new Set(operators));
|
|
1575
2624
|
const parameter = node.typeArguments?.params[0];
|
|
@@ -1586,9 +2635,98 @@ var noRederiveSchema = defineRule15({
|
|
|
1586
2635
|
}
|
|
1587
2636
|
});
|
|
1588
2637
|
|
|
2638
|
+
// src/rules/no-restricted-constructor.ts
|
|
2639
|
+
import { defineRule as defineRule24 } from "@oxlint/plugins";
|
|
2640
|
+
function restrictedConstructorEntries(constructors) {
|
|
2641
|
+
const entries = new Map;
|
|
2642
|
+
for (const item of constructors) {
|
|
2643
|
+
if (typeof item === "string") {
|
|
2644
|
+
if (!entries.has(item)) {
|
|
2645
|
+
entries.set(item, undefined);
|
|
2646
|
+
}
|
|
2647
|
+
continue;
|
|
2648
|
+
}
|
|
2649
|
+
if (!entries.has(item.name)) {
|
|
2650
|
+
entries.set(item.name, item.message);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
return entries;
|
|
2654
|
+
}
|
|
2655
|
+
var noRestrictedConstructor = defineRule24({
|
|
2656
|
+
meta: {
|
|
2657
|
+
type: "problem",
|
|
2658
|
+
schema: [{
|
|
2659
|
+
type: "object",
|
|
2660
|
+
additionalProperties: false,
|
|
2661
|
+
properties: {
|
|
2662
|
+
constructors: {
|
|
2663
|
+
type: "array",
|
|
2664
|
+
items: {
|
|
2665
|
+
anyOf: [
|
|
2666
|
+
{ type: "string" },
|
|
2667
|
+
{
|
|
2668
|
+
type: "object",
|
|
2669
|
+
additionalProperties: false,
|
|
2670
|
+
properties: {
|
|
2671
|
+
name: { type: "string" },
|
|
2672
|
+
message: { type: "string" }
|
|
2673
|
+
},
|
|
2674
|
+
required: ["name"]
|
|
2675
|
+
}
|
|
2676
|
+
]
|
|
2677
|
+
}
|
|
2678
|
+
},
|
|
2679
|
+
checkCalls: { type: "boolean", default: false },
|
|
2680
|
+
message: { type: "string" }
|
|
2681
|
+
},
|
|
2682
|
+
required: ["constructors"]
|
|
2683
|
+
}],
|
|
2684
|
+
messages: {
|
|
2685
|
+
construct: "Do not construct '{{name}}'.{{suffix}}",
|
|
2686
|
+
call: "Do not call '{{name}}'.{{suffix}}"
|
|
2687
|
+
}
|
|
2688
|
+
},
|
|
2689
|
+
createOnce(context) {
|
|
2690
|
+
let entries = new Map;
|
|
2691
|
+
let checkCalls = false;
|
|
2692
|
+
let fallbackMessage;
|
|
2693
|
+
function report(node, messageId) {
|
|
2694
|
+
const name = astDottedName(node.callee);
|
|
2695
|
+
if (!name || !entries.has(name)) {
|
|
2696
|
+
return;
|
|
2697
|
+
}
|
|
2698
|
+
const extra = entries.get(name) ?? fallbackMessage;
|
|
2699
|
+
context.report({
|
|
2700
|
+
node,
|
|
2701
|
+
messageId,
|
|
2702
|
+
data: { name, suffix: extra ? ` ${extra}` : "" }
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
return {
|
|
2706
|
+
before() {
|
|
2707
|
+
const options = optionsFirst(context);
|
|
2708
|
+
entries = restrictedConstructorEntries(options.constructors);
|
|
2709
|
+
checkCalls = options.checkCalls === true;
|
|
2710
|
+
fallbackMessage = options.message;
|
|
2711
|
+
if (entries.size === 0) {
|
|
2712
|
+
return false;
|
|
2713
|
+
}
|
|
2714
|
+
},
|
|
2715
|
+
NewExpression(node) {
|
|
2716
|
+
report(node, "construct");
|
|
2717
|
+
},
|
|
2718
|
+
CallExpression(node) {
|
|
2719
|
+
if (checkCalls) {
|
|
2720
|
+
report(node, "call");
|
|
2721
|
+
}
|
|
2722
|
+
}
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
});
|
|
2726
|
+
|
|
1589
2727
|
// src/rules/no-restricted-files.ts
|
|
1590
|
-
import { defineRule as
|
|
1591
|
-
var noRestrictedFiles =
|
|
2728
|
+
import { defineRule as defineRule25 } from "@oxlint/plugins";
|
|
2729
|
+
var noRestrictedFiles = defineRule25({
|
|
1592
2730
|
meta: {
|
|
1593
2731
|
type: "problem",
|
|
1594
2732
|
schema: [{
|
|
@@ -1618,7 +2756,7 @@ var noRestrictedFiles = defineRule16({
|
|
|
1618
2756
|
});
|
|
1619
2757
|
|
|
1620
2758
|
// src/rules/no-restricted-token.ts
|
|
1621
|
-
import { defineRule as
|
|
2759
|
+
import { defineRule as defineRule26 } from "@oxlint/plugins";
|
|
1622
2760
|
function tokenIsTypeOnlyImport(node) {
|
|
1623
2761
|
const parent = node.parent;
|
|
1624
2762
|
if (parent?.type === "ImportSpecifier") {
|
|
@@ -1626,7 +2764,10 @@ function tokenIsTypeOnlyImport(node) {
|
|
|
1626
2764
|
}
|
|
1627
2765
|
return (parent?.type === "ImportDefaultSpecifier" || parent?.type === "ImportNamespaceSpecifier") && parent.parent.type === "ImportDeclaration" && parent.parent.importKind === "type";
|
|
1628
2766
|
}
|
|
1629
|
-
|
|
2767
|
+
function memberMatches(node, restriction) {
|
|
2768
|
+
return restriction.startsWith("*.") ? astStaticMemberName(node) === restriction.slice(2) : astDottedName(node) === restriction;
|
|
2769
|
+
}
|
|
2770
|
+
var noRestrictedToken = defineRule26({
|
|
1630
2771
|
meta: {
|
|
1631
2772
|
type: "problem",
|
|
1632
2773
|
schema: [{
|
|
@@ -1635,32 +2776,86 @@ var noRestrictedToken = defineRule17({
|
|
|
1635
2776
|
properties: {
|
|
1636
2777
|
token: { type: "string" },
|
|
1637
2778
|
allowIn: { type: "array", items: { type: "string" } },
|
|
1638
|
-
allowPathPatterns: { type: "array", items: { type: "string" } }
|
|
2779
|
+
allowPathPatterns: { type: "array", items: { type: "string" } },
|
|
2780
|
+
restrictions: {
|
|
2781
|
+
type: "array",
|
|
2782
|
+
items: {
|
|
2783
|
+
type: "object",
|
|
2784
|
+
additionalProperties: false,
|
|
2785
|
+
properties: {
|
|
2786
|
+
token: { type: "string" },
|
|
2787
|
+
member: { type: "string" },
|
|
2788
|
+
allowIn: { type: "array", items: { type: "string" } },
|
|
2789
|
+
allowPathPatterns: { type: "array", items: { type: "string" } },
|
|
2790
|
+
message: { type: "string" }
|
|
2791
|
+
},
|
|
2792
|
+
oneOf: [
|
|
2793
|
+
{ required: ["token"], not: { required: ["member"] } },
|
|
2794
|
+
{ required: ["member"], not: { required: ["token"] } }
|
|
2795
|
+
]
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
1639
2798
|
},
|
|
1640
|
-
|
|
2799
|
+
anyOf: [
|
|
2800
|
+
{ required: ["token"] },
|
|
2801
|
+
{ required: ["restrictions"] }
|
|
2802
|
+
]
|
|
1641
2803
|
}],
|
|
1642
2804
|
messages: {
|
|
1643
|
-
restricted: "'{{token}}' may only appear in configured owner paths."
|
|
2805
|
+
restricted: "'{{token}}' may only appear in configured owner paths.",
|
|
2806
|
+
restrictedWithMessage: "'{{token}}' may only appear in configured owner paths. {{custom}}"
|
|
1644
2807
|
}
|
|
1645
2808
|
},
|
|
1646
2809
|
createOnce(context) {
|
|
2810
|
+
let restrictions = [];
|
|
2811
|
+
function report(node, restriction) {
|
|
2812
|
+
context.report({
|
|
2813
|
+
node,
|
|
2814
|
+
messageId: restriction.message === undefined ? "restricted" : "restrictedWithMessage",
|
|
2815
|
+
data: { token: restriction.value, custom: restriction.message ?? "" }
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
1647
2818
|
return {
|
|
1648
2819
|
before() {
|
|
1649
2820
|
const options = optionsFirst(context);
|
|
2821
|
+
const configured = [
|
|
2822
|
+
...options.token ? [{
|
|
2823
|
+
token: options.token,
|
|
2824
|
+
allowIn: options.allowIn,
|
|
2825
|
+
allowPathPatterns: options.allowPathPatterns
|
|
2826
|
+
}] : [],
|
|
2827
|
+
...options.restrictions ?? []
|
|
2828
|
+
];
|
|
1650
2829
|
const filename = namingPosixPath(context.filename);
|
|
1651
|
-
|
|
1652
|
-
|
|
2830
|
+
restrictions = configured.flatMap((restriction) => {
|
|
2831
|
+
const allowed = (restriction.allowIn ?? []).some((suffix) => filename.endsWith(suffix)) || (restriction.allowPathPatterns ?? []).some((pattern) => new RegExp(pattern).test(filename));
|
|
2832
|
+
if (allowed) {
|
|
2833
|
+
return [];
|
|
2834
|
+
}
|
|
2835
|
+
if (restriction.token) {
|
|
2836
|
+
return [{ kind: "token", value: restriction.token, message: restriction.message }];
|
|
2837
|
+
}
|
|
2838
|
+
return restriction.member ? [{ kind: "member", value: restriction.member, message: restriction.message }] : [];
|
|
2839
|
+
});
|
|
2840
|
+
if (restrictions.length === 0) {
|
|
1653
2841
|
return false;
|
|
1654
2842
|
}
|
|
1655
2843
|
},
|
|
1656
2844
|
Identifier(node) {
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
}
|
|
2845
|
+
if (tokenIsTypeOnlyImport(node)) {
|
|
2846
|
+
return;
|
|
2847
|
+
}
|
|
2848
|
+
for (const restriction of restrictions) {
|
|
2849
|
+
if (restriction.kind === "token" && node.name === restriction.value) {
|
|
2850
|
+
report(node, restriction);
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
},
|
|
2854
|
+
MemberExpression(node) {
|
|
2855
|
+
for (const restriction of restrictions) {
|
|
2856
|
+
if (restriction.kind === "member" && memberMatches(node, restriction.value)) {
|
|
2857
|
+
report(node, restriction);
|
|
2858
|
+
}
|
|
1664
2859
|
}
|
|
1665
2860
|
}
|
|
1666
2861
|
};
|
|
@@ -1668,8 +2863,224 @@ var noRestrictedToken = defineRule17({
|
|
|
1668
2863
|
});
|
|
1669
2864
|
|
|
1670
2865
|
// src/rules/no-runtime-in-types.ts
|
|
1671
|
-
import { defineRule as
|
|
1672
|
-
|
|
2866
|
+
import { defineRule as defineRule27 } from "@oxlint/plugins";
|
|
2867
|
+
function isTypeOnlyImport(statement) {
|
|
2868
|
+
return statement.importKind === "type" || statement.specifiers.length > 0 && statement.specifiers.every((specifier) => specifier.type === "ImportSpecifier" && specifier.importKind === "type");
|
|
2869
|
+
}
|
|
2870
|
+
function exportLocalName(specifier) {
|
|
2871
|
+
return specifier.local.type === "Identifier" ? specifier.local.name : specifier.local.value;
|
|
2872
|
+
}
|
|
2873
|
+
function importEqualsSource(declaration) {
|
|
2874
|
+
return declaration.moduleReference.type === "TSExternalModuleReference" ? declaration.moduleReference.expression.value : undefined;
|
|
2875
|
+
}
|
|
2876
|
+
function importEqualsDeclaration(statement) {
|
|
2877
|
+
if (statement.type === "TSImportEqualsDeclaration") {
|
|
2878
|
+
return statement;
|
|
2879
|
+
}
|
|
2880
|
+
return statement.type === "ExportNamedDeclaration" && statement.declaration?.type === "TSImportEqualsDeclaration" ? statement.declaration : undefined;
|
|
2881
|
+
}
|
|
2882
|
+
function isAllowedSource(source, allowedImportSources) {
|
|
2883
|
+
return allowedImportSources.some((pattern) => {
|
|
2884
|
+
pattern.lastIndex = 0;
|
|
2885
|
+
return pattern.test(source);
|
|
2886
|
+
});
|
|
2887
|
+
}
|
|
2888
|
+
function runtimeInTypesIsAmbientFile(filename) {
|
|
2889
|
+
return filename.endsWith(".d.ts") || filename.endsWith(".d.mts") || filename.endsWith(".d.cts");
|
|
2890
|
+
}
|
|
2891
|
+
function runtimeInTypesUnwrapped(statement) {
|
|
2892
|
+
return statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration ?? undefined : statement;
|
|
2893
|
+
}
|
|
2894
|
+
function runtimeInTypesEntityRootName(node) {
|
|
2895
|
+
let current = node;
|
|
2896
|
+
while (current.type === "TSQualifiedName") {
|
|
2897
|
+
current = current.left;
|
|
2898
|
+
}
|
|
2899
|
+
return current.type === "Identifier" ? current.name : undefined;
|
|
2900
|
+
}
|
|
2901
|
+
function importEqualsRootName(declaration) {
|
|
2902
|
+
return declaration.moduleReference.type === "TSExternalModuleReference" ? undefined : runtimeInTypesEntityRootName(declaration.moduleReference);
|
|
2903
|
+
}
|
|
2904
|
+
function runtimeInTypesModuleBindingName(node) {
|
|
2905
|
+
return node.type === "TSModuleDeclaration" ? runtimeInTypesEntityRootName(node.id) : undefined;
|
|
2906
|
+
}
|
|
2907
|
+
function runtimeInTypesIsInternalValueAlias(declaration, valueNames) {
|
|
2908
|
+
if (declaration.importKind === "type" || importEqualsSource(declaration) !== undefined) {
|
|
2909
|
+
return false;
|
|
2910
|
+
}
|
|
2911
|
+
const root = importEqualsRootName(declaration);
|
|
2912
|
+
return root !== undefined && valueNames.has(root);
|
|
2913
|
+
}
|
|
2914
|
+
function runtimeInTypesModuleName(node) {
|
|
2915
|
+
if (node.type !== "TSModuleDeclaration") {
|
|
2916
|
+
return "namespace";
|
|
2917
|
+
}
|
|
2918
|
+
return node.id.type === "Identifier" ? node.id.name : node.id.type === "Literal" ? String(node.id.value) : "namespace";
|
|
2919
|
+
}
|
|
2920
|
+
function runtimeInTypesNamespaceIsRuntime(node, isAmbientFile, valueNames) {
|
|
2921
|
+
if (isAmbientFile || node.type !== "TSModuleDeclaration" || node.declare || node.global || node.id.type === "Literal" || !node.body) {
|
|
2922
|
+
return false;
|
|
2923
|
+
}
|
|
2924
|
+
return node.body.body.some((statement) => runtimeInTypesNamespaceMemberIsRuntime(statement, isAmbientFile, valueNames));
|
|
2925
|
+
}
|
|
2926
|
+
function runtimeInTypesNamespaceMemberIsRuntime(node, isAmbientFile, valueNames) {
|
|
2927
|
+
if (node.type === "ExportNamedDeclaration") {
|
|
2928
|
+
if (node.exportKind === "type") {
|
|
2929
|
+
return false;
|
|
2930
|
+
}
|
|
2931
|
+
return node.declaration ? runtimeInTypesNamespaceMemberIsRuntime(node.declaration, isAmbientFile, valueNames) : node.specifiers.some((specifier) => specifier.exportKind !== "type");
|
|
2932
|
+
}
|
|
2933
|
+
if (node.type === "TSModuleDeclaration") {
|
|
2934
|
+
return runtimeInTypesNamespaceIsRuntime(node, isAmbientFile, valueNames);
|
|
2935
|
+
}
|
|
2936
|
+
if (node.type === "TSImportEqualsDeclaration") {
|
|
2937
|
+
return node.importKind !== "type" && (node.moduleReference.type === "TSExternalModuleReference" || runtimeInTypesIsInternalValueAlias(node, valueNames));
|
|
2938
|
+
}
|
|
2939
|
+
if (node.type === "TSExportAssignment") {
|
|
2940
|
+
return true;
|
|
2941
|
+
}
|
|
2942
|
+
return declarationsIsRuntime(node) && !node.declare;
|
|
2943
|
+
}
|
|
2944
|
+
function runtimeInTypesMemberHasValue(node) {
|
|
2945
|
+
if (node.type === "ExportNamedDeclaration") {
|
|
2946
|
+
if (node.exportKind === "type") {
|
|
2947
|
+
return false;
|
|
2948
|
+
}
|
|
2949
|
+
return node.declaration ? runtimeInTypesMemberHasValue(node.declaration) : node.specifiers.some((specifier) => specifier.exportKind !== "type");
|
|
2950
|
+
}
|
|
2951
|
+
if (node.type === "TSModuleDeclaration") {
|
|
2952
|
+
return runtimeInTypesNamespaceHasValueMembers(node);
|
|
2953
|
+
}
|
|
2954
|
+
if (node.type === "TSImportEqualsDeclaration") {
|
|
2955
|
+
return node.importKind !== "type" && node.moduleReference.type === "TSExternalModuleReference";
|
|
2956
|
+
}
|
|
2957
|
+
if (node.type === "TSExportAssignment") {
|
|
2958
|
+
return true;
|
|
2959
|
+
}
|
|
2960
|
+
return declarationsIsRuntime(node);
|
|
2961
|
+
}
|
|
2962
|
+
function runtimeInTypesNamespaceHasValueMembers(node) {
|
|
2963
|
+
if (node.type !== "TSModuleDeclaration" || node.global || node.id.type === "Literal" || !node.body) {
|
|
2964
|
+
return false;
|
|
2965
|
+
}
|
|
2966
|
+
return node.body.body.some((statement) => runtimeInTypesMemberHasValue(statement));
|
|
2967
|
+
}
|
|
2968
|
+
function runtimeInTypesMemberReferencesValue(node, valueNames) {
|
|
2969
|
+
if (node.type === "ExportNamedDeclaration") {
|
|
2970
|
+
return node.declaration ? runtimeInTypesMemberReferencesValue(node.declaration, valueNames) : false;
|
|
2971
|
+
}
|
|
2972
|
+
if (node.type === "TSModuleDeclaration") {
|
|
2973
|
+
return runtimeInTypesNamespaceHasValueMembers(node) || runtimeInTypesNamespaceReferencesValue(node, valueNames);
|
|
2974
|
+
}
|
|
2975
|
+
if (node.type === "TSImportEqualsDeclaration") {
|
|
2976
|
+
return runtimeInTypesIsInternalValueAlias(node, valueNames);
|
|
2977
|
+
}
|
|
2978
|
+
return false;
|
|
2979
|
+
}
|
|
2980
|
+
function runtimeInTypesNamespaceReferencesValue(node, valueNames) {
|
|
2981
|
+
if (node.type !== "TSModuleDeclaration" || !node.body) {
|
|
2982
|
+
return false;
|
|
2983
|
+
}
|
|
2984
|
+
return node.body.body.some((statement) => runtimeInTypesMemberReferencesValue(statement, valueNames));
|
|
2985
|
+
}
|
|
2986
|
+
function runtimeInTypesSeedValueName(declaration, valueNames) {
|
|
2987
|
+
if (declaration.type === "VariableDeclaration") {
|
|
2988
|
+
for (const item of declaration.declarations) {
|
|
2989
|
+
if (item.id.type === "Identifier") {
|
|
2990
|
+
valueNames.add(item.id.name);
|
|
2991
|
+
}
|
|
2992
|
+
}
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
2995
|
+
if ((declaration.type === "FunctionDeclaration" || declaration.type === "ClassDeclaration" || declaration.type === "TSEnumDeclaration") && declaration.id) {
|
|
2996
|
+
valueNames.add(declaration.id.name);
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
if (declaration.type === "TSModuleDeclaration") {
|
|
3000
|
+
const name = runtimeInTypesModuleBindingName(declaration);
|
|
3001
|
+
if (name && runtimeInTypesNamespaceHasValueMembers(declaration)) {
|
|
3002
|
+
valueNames.add(name);
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
function runtimeInTypesValueNames(statements) {
|
|
3007
|
+
const valueNames = new Set;
|
|
3008
|
+
for (const statement of statements) {
|
|
3009
|
+
if (statement.type === "ImportDeclaration") {
|
|
3010
|
+
if (isTypeOnlyImport(statement)) {
|
|
3011
|
+
continue;
|
|
3012
|
+
}
|
|
3013
|
+
for (const specifier of statement.specifiers) {
|
|
3014
|
+
if (specifier.type !== "ImportSpecifier" || specifier.importKind !== "type") {
|
|
3015
|
+
valueNames.add(specifier.local.name);
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
continue;
|
|
3019
|
+
}
|
|
3020
|
+
const importEquals = importEqualsDeclaration(statement);
|
|
3021
|
+
if (importEquals) {
|
|
3022
|
+
if (importEquals.importKind !== "type" && importEqualsSource(importEquals) !== undefined) {
|
|
3023
|
+
valueNames.add(importEquals.id.name);
|
|
3024
|
+
}
|
|
3025
|
+
continue;
|
|
3026
|
+
}
|
|
3027
|
+
const declaration = runtimeInTypesUnwrapped(statement);
|
|
3028
|
+
if (declaration) {
|
|
3029
|
+
runtimeInTypesSeedValueName(declaration, valueNames);
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
let changed = true;
|
|
3033
|
+
while (changed) {
|
|
3034
|
+
changed = false;
|
|
3035
|
+
for (const statement of statements) {
|
|
3036
|
+
const importEquals = importEqualsDeclaration(statement);
|
|
3037
|
+
if (importEquals && runtimeInTypesIsInternalValueAlias(importEquals, valueNames)) {
|
|
3038
|
+
if (!valueNames.has(importEquals.id.name)) {
|
|
3039
|
+
valueNames.add(importEquals.id.name);
|
|
3040
|
+
changed = true;
|
|
3041
|
+
}
|
|
3042
|
+
continue;
|
|
3043
|
+
}
|
|
3044
|
+
const declaration = runtimeInTypesUnwrapped(statement);
|
|
3045
|
+
if (declaration?.type !== "TSModuleDeclaration") {
|
|
3046
|
+
continue;
|
|
3047
|
+
}
|
|
3048
|
+
const name = runtimeInTypesModuleBindingName(declaration);
|
|
3049
|
+
if (name && !valueNames.has(name) && runtimeInTypesNamespaceReferencesValue(declaration, valueNames)) {
|
|
3050
|
+
valueNames.add(name);
|
|
3051
|
+
changed = true;
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
3054
|
+
}
|
|
3055
|
+
return valueNames;
|
|
3056
|
+
}
|
|
3057
|
+
function runtimeInTypesExportAssignmentExpression(node) {
|
|
3058
|
+
let expression = node.expression;
|
|
3059
|
+
while (expression.type === "ParenthesizedExpression") {
|
|
3060
|
+
expression = expression.expression;
|
|
3061
|
+
}
|
|
3062
|
+
return expression;
|
|
3063
|
+
}
|
|
3064
|
+
function isRuntimeReExport(statement, runtimeImportLocals, valueNames, isAmbientFile) {
|
|
3065
|
+
if (statement.exportKind === "type") {
|
|
3066
|
+
return false;
|
|
3067
|
+
}
|
|
3068
|
+
if (statement.type === "ExportAllDeclaration") {
|
|
3069
|
+
return true;
|
|
3070
|
+
}
|
|
3071
|
+
if (statement.declaration?.type === "TSImportEqualsDeclaration") {
|
|
3072
|
+
const declaration = statement.declaration;
|
|
3073
|
+
if (declaration.importKind === "type" || isAmbientFile) {
|
|
3074
|
+
return false;
|
|
3075
|
+
}
|
|
3076
|
+
return importEqualsSource(declaration) !== undefined || runtimeInTypesIsInternalValueAlias(declaration, valueNames);
|
|
3077
|
+
}
|
|
3078
|
+
if (statement.source) {
|
|
3079
|
+
return statement.specifiers.length === 0 || statement.specifiers.some((specifier) => specifier.exportKind !== "type");
|
|
3080
|
+
}
|
|
3081
|
+
return statement.specifiers.some((specifier) => specifier.exportKind !== "type" && runtimeImportLocals.has(exportLocalName(specifier)));
|
|
3082
|
+
}
|
|
3083
|
+
var noRuntimeInTypes = defineRule27({
|
|
1673
3084
|
meta: {
|
|
1674
3085
|
type: "problem",
|
|
1675
3086
|
schema: [{
|
|
@@ -1679,18 +3090,80 @@ var noRuntimeInTypes = defineRule18({
|
|
|
1679
3090
|
allow: {
|
|
1680
3091
|
type: "array",
|
|
1681
3092
|
items: { type: "string", enum: ["class", "default", "enum", "function", "variable"] }
|
|
3093
|
+
},
|
|
3094
|
+
runtimeImports: { type: "string", enum: ["allow", "ban"] },
|
|
3095
|
+
allowImportSources: { type: "array", items: { type: "string" } },
|
|
3096
|
+
banReExports: { type: "boolean" }
|
|
3097
|
+
}
|
|
3098
|
+
}],
|
|
3099
|
+
messages: {
|
|
3100
|
+
runtimeValue: "Types files must not contain runtime value {{name}}.",
|
|
3101
|
+
runtimeImport: "Types files must not contain runtime imports from {{source}}.",
|
|
3102
|
+
runtimeReExport: "Types files must not contain runtime re-exports."
|
|
3103
|
+
}
|
|
3104
|
+
},
|
|
3105
|
+
createOnce(context) {
|
|
3106
|
+
let allow = [];
|
|
3107
|
+
let runtimeImports = "allow";
|
|
3108
|
+
let allowedImportSources = [];
|
|
3109
|
+
let banReExports = false;
|
|
3110
|
+
let isAmbientFile = false;
|
|
3111
|
+
return {
|
|
3112
|
+
before() {
|
|
3113
|
+
const options = optionsFirst(context, {});
|
|
3114
|
+
allow = options.allow ?? [];
|
|
3115
|
+
runtimeImports = options.runtimeImports ?? "allow";
|
|
3116
|
+
allowedImportSources = (options.allowImportSources ?? []).map((pattern) => new RegExp(pattern));
|
|
3117
|
+
banReExports = options.banReExports === true;
|
|
3118
|
+
isAmbientFile = runtimeInTypesIsAmbientFile(context.filename);
|
|
3119
|
+
},
|
|
3120
|
+
Program(program) {
|
|
3121
|
+
const runtimeImportLocals = new Set;
|
|
3122
|
+
const valueNames = runtimeInTypesValueNames(program.body);
|
|
3123
|
+
for (const statement of program.body) {
|
|
3124
|
+
if (statement.type === "ImportDeclaration") {
|
|
3125
|
+
if (isTypeOnlyImport(statement)) {
|
|
3126
|
+
continue;
|
|
3127
|
+
}
|
|
3128
|
+
for (const specifier of statement.specifiers) {
|
|
3129
|
+
if (specifier.type !== "ImportSpecifier" || specifier.importKind !== "type") {
|
|
3130
|
+
runtimeImportLocals.add(specifier.local.name);
|
|
3131
|
+
}
|
|
3132
|
+
}
|
|
3133
|
+
const source = String(statement.source.value);
|
|
3134
|
+
if (runtimeImports === "ban" && !isAllowedSource(source, allowedImportSources)) {
|
|
3135
|
+
context.report({
|
|
3136
|
+
node: statement,
|
|
3137
|
+
messageId: "runtimeImport",
|
|
3138
|
+
data: { source }
|
|
3139
|
+
});
|
|
3140
|
+
}
|
|
3141
|
+
continue;
|
|
3142
|
+
}
|
|
3143
|
+
const declaration = importEqualsDeclaration(statement);
|
|
3144
|
+
if (!declaration || declaration.importKind === "type") {
|
|
3145
|
+
continue;
|
|
3146
|
+
}
|
|
3147
|
+
const source = importEqualsSource(declaration);
|
|
3148
|
+
if (source === undefined) {
|
|
3149
|
+
continue;
|
|
3150
|
+
}
|
|
3151
|
+
runtimeImportLocals.add(declaration.id.name);
|
|
3152
|
+
if (runtimeImports === "ban" && !isAllowedSource(source, allowedImportSources)) {
|
|
3153
|
+
context.report({
|
|
3154
|
+
node: declaration,
|
|
3155
|
+
messageId: "runtimeImport",
|
|
3156
|
+
data: { source }
|
|
3157
|
+
});
|
|
3158
|
+
}
|
|
1682
3159
|
}
|
|
1683
|
-
}
|
|
1684
|
-
}],
|
|
1685
|
-
messages: {
|
|
1686
|
-
runtimeValue: "Types files must not contain runtime value {{name}}."
|
|
1687
|
-
}
|
|
1688
|
-
},
|
|
1689
|
-
createOnce(context) {
|
|
1690
|
-
return {
|
|
1691
|
-
Program(program) {
|
|
1692
|
-
const { allow = [] } = optionsFirst(context, {});
|
|
1693
3160
|
for (const statement of program.body) {
|
|
3161
|
+
if (banReExports && (statement.type === "ExportNamedDeclaration" || statement.type === "ExportAllDeclaration") && isRuntimeReExport(statement, runtimeImportLocals, valueNames, isAmbientFile)) {
|
|
3162
|
+
context.report({
|
|
3163
|
+
node: statement,
|
|
3164
|
+
messageId: "runtimeReExport"
|
|
3165
|
+
});
|
|
3166
|
+
}
|
|
1694
3167
|
const declaration = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
|
|
1695
3168
|
if (!declaration) {
|
|
1696
3169
|
continue;
|
|
@@ -1705,6 +3178,36 @@ var noRuntimeInTypes = defineRule18({
|
|
|
1705
3178
|
}
|
|
1706
3179
|
continue;
|
|
1707
3180
|
}
|
|
3181
|
+
if (declaration.type === "TSModuleDeclaration") {
|
|
3182
|
+
if (runtimeInTypesNamespaceIsRuntime(declaration, isAmbientFile, valueNames)) {
|
|
3183
|
+
context.report({
|
|
3184
|
+
node: declaration,
|
|
3185
|
+
messageId: "runtimeValue",
|
|
3186
|
+
data: { name: runtimeInTypesModuleName(declaration) }
|
|
3187
|
+
});
|
|
3188
|
+
}
|
|
3189
|
+
continue;
|
|
3190
|
+
}
|
|
3191
|
+
if (statement.type === "TSExportAssignment" && !allow.includes("default")) {
|
|
3192
|
+
const expression = runtimeInTypesExportAssignmentExpression(statement);
|
|
3193
|
+
if (expression.type !== "Identifier" || valueNames.has(expression.name)) {
|
|
3194
|
+
context.report({
|
|
3195
|
+
node: statement,
|
|
3196
|
+
messageId: "runtimeValue",
|
|
3197
|
+
data: { name: expression.type === "Identifier" ? expression.name : "default" }
|
|
3198
|
+
});
|
|
3199
|
+
}
|
|
3200
|
+
continue;
|
|
3201
|
+
}
|
|
3202
|
+
const importEquals = importEqualsDeclaration(statement);
|
|
3203
|
+
if (!isAmbientFile && importEquals && runtimeInTypesIsInternalValueAlias(importEquals, valueNames)) {
|
|
3204
|
+
context.report({
|
|
3205
|
+
node: importEquals,
|
|
3206
|
+
messageId: "runtimeValue",
|
|
3207
|
+
data: { name: importEquals.id.name }
|
|
3208
|
+
});
|
|
3209
|
+
continue;
|
|
3210
|
+
}
|
|
1708
3211
|
if (!allow.includes("default") && statement.type === "ExportDefaultDeclaration" && declaration.type !== "TSInterfaceDeclaration" && declaration.type !== "TSTypeAliasDeclaration") {
|
|
1709
3212
|
context.report({
|
|
1710
3213
|
node: statement,
|
|
@@ -1719,7 +3222,7 @@ var noRuntimeInTypes = defineRule18({
|
|
|
1719
3222
|
});
|
|
1720
3223
|
|
|
1721
3224
|
// src/rules/no-single-use-scalar-schema.ts
|
|
1722
|
-
import { defineRule as
|
|
3225
|
+
import { defineRule as defineRule28 } from "@oxlint/plugins";
|
|
1723
3226
|
function isScalarSchema(node, namespaces, structuralMethods) {
|
|
1724
3227
|
if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") {
|
|
1725
3228
|
return false;
|
|
@@ -1753,7 +3256,7 @@ function isAllowedStructuralElement(identifier, initializer, ancestors, options,
|
|
|
1753
3256
|
const method = call.callee.type === "MemberExpression" ? astStaticMemberName(call.callee) : undefined;
|
|
1754
3257
|
return Boolean(method && options.methods.includes(method) && schemasElements(call).includes(identifier));
|
|
1755
3258
|
}
|
|
1756
|
-
var noSingleUseScalarSchema =
|
|
3259
|
+
var noSingleUseScalarSchema = defineRule28({
|
|
1757
3260
|
meta: {
|
|
1758
3261
|
type: "suggestion",
|
|
1759
3262
|
schema: [{
|
|
@@ -1821,8 +3324,8 @@ var noSingleUseScalarSchema = defineRule19({
|
|
|
1821
3324
|
});
|
|
1822
3325
|
|
|
1823
3326
|
// src/rules/no-top-level-functions.ts
|
|
1824
|
-
import { defineRule as
|
|
1825
|
-
var noTopLevelFunctions =
|
|
3327
|
+
import { defineRule as defineRule29 } from "@oxlint/plugins";
|
|
3328
|
+
var noTopLevelFunctions = defineRule29({
|
|
1826
3329
|
meta: {
|
|
1827
3330
|
type: "problem",
|
|
1828
3331
|
schema: [{
|
|
@@ -1879,15 +3382,43 @@ var noTopLevelFunctions = defineRule20({
|
|
|
1879
3382
|
});
|
|
1880
3383
|
|
|
1881
3384
|
// src/rules/no-trivial-functions.ts
|
|
1882
|
-
import { defineRule as
|
|
1883
|
-
|
|
3385
|
+
import { defineRule as defineRule30 } from "@oxlint/plugins";
|
|
3386
|
+
function forwardedExpression(node) {
|
|
3387
|
+
if (node.type === "ArrowFunctionExpression" && node.body.type !== "BlockStatement") {
|
|
3388
|
+
return node.body;
|
|
3389
|
+
}
|
|
3390
|
+
if (!node.body || node.body.type !== "BlockStatement") {
|
|
3391
|
+
return null;
|
|
3392
|
+
}
|
|
3393
|
+
const statements = node.body.body.filter((statement) => statement.type !== "EmptyStatement" && !(statement.type === "ExpressionStatement" && statement.directive));
|
|
3394
|
+
if (statements.length !== 1) {
|
|
3395
|
+
return null;
|
|
3396
|
+
}
|
|
3397
|
+
const statement = statements[0];
|
|
3398
|
+
if (statement.type === "ReturnStatement") {
|
|
3399
|
+
return statement.argument;
|
|
3400
|
+
}
|
|
3401
|
+
return statement.type === "ExpressionStatement" ? statement.expression : null;
|
|
3402
|
+
}
|
|
3403
|
+
function forwardedCall(expression) {
|
|
3404
|
+
if (expression.type === "ParenthesizedExpression" || expression.type === "TSAsExpression" || expression.type === "TSSatisfiesExpression" || expression.type === "TSTypeAssertion" || expression.type === "TSNonNullExpression" || expression.type === "ChainExpression") {
|
|
3405
|
+
return forwardedCall(expression.expression);
|
|
3406
|
+
}
|
|
3407
|
+
if (expression.type === "AwaitExpression") {
|
|
3408
|
+
return forwardedCall(expression.argument);
|
|
3409
|
+
}
|
|
3410
|
+
return expression.type === "CallExpression" ? expression : null;
|
|
3411
|
+
}
|
|
3412
|
+
var noTrivialFunctions = defineRule30({
|
|
1884
3413
|
meta: {
|
|
1885
3414
|
type: "problem",
|
|
1886
3415
|
schema: [{
|
|
1887
3416
|
type: "object",
|
|
1888
3417
|
additionalProperties: false,
|
|
1889
3418
|
properties: {
|
|
1890
|
-
allowPattern: { type: "string" }
|
|
3419
|
+
allowPattern: { type: "string" },
|
|
3420
|
+
allowCallees: { type: "array", items: { type: "string" } },
|
|
3421
|
+
allowAsync: { type: "boolean" }
|
|
1891
3422
|
}
|
|
1892
3423
|
}],
|
|
1893
3424
|
messages: {
|
|
@@ -1895,16 +3426,29 @@ var noTrivialFunctions = defineRule21({
|
|
|
1895
3426
|
}
|
|
1896
3427
|
},
|
|
1897
3428
|
createOnce(context) {
|
|
3429
|
+
let allowed = null;
|
|
3430
|
+
let allowedCallees = [];
|
|
3431
|
+
let allowAsync = false;
|
|
1898
3432
|
return {
|
|
3433
|
+
before() {
|
|
3434
|
+
const { allowPattern, allowCallees: calleePatterns = [], allowAsync: asyncAllowed = false } = optionsFirst(context, {});
|
|
3435
|
+
allowed = allowPattern ? new RegExp(allowPattern) : null;
|
|
3436
|
+
allowedCallees = calleePatterns.map((pattern) => new RegExp(pattern));
|
|
3437
|
+
allowAsync = asyncAllowed;
|
|
3438
|
+
},
|
|
1899
3439
|
Program(program) {
|
|
1900
|
-
const { allowPattern } = optionsFirst(context, {});
|
|
1901
|
-
const allowed = allowPattern ? new RegExp(allowPattern) : null;
|
|
1902
3440
|
const seen = new Set;
|
|
1903
3441
|
for (const item of [...exportsCollectFunctions(program), ...declarationsTopLevelUnexportedFunctions(program)]) {
|
|
1904
3442
|
const key = `${item.name}:${item.node.start}:${item.node.end}`;
|
|
1905
3443
|
if (seen.has(key) || allowed?.test(item.name) || !declarationsIsTrivialFunction(item.node)) {
|
|
1906
3444
|
continue;
|
|
1907
3445
|
}
|
|
3446
|
+
const expression = forwardedExpression(item.node);
|
|
3447
|
+
const call = expression && forwardedCall(expression);
|
|
3448
|
+
const path = call && astDottedName(call.callee);
|
|
3449
|
+
if (allowAsync && item.node.async && call || path && allowedCallees.some((pattern) => pattern.test(path))) {
|
|
3450
|
+
continue;
|
|
3451
|
+
}
|
|
1908
3452
|
seen.add(key);
|
|
1909
3453
|
context.report({
|
|
1910
3454
|
node: item.node,
|
|
@@ -1918,8 +3462,8 @@ var noTrivialFunctions = defineRule21({
|
|
|
1918
3462
|
});
|
|
1919
3463
|
|
|
1920
3464
|
// src/rules/no-type-declarations.ts
|
|
1921
|
-
import { defineRule as
|
|
1922
|
-
var noTypeDeclarations =
|
|
3465
|
+
import { defineRule as defineRule31 } from "@oxlint/plugins";
|
|
3466
|
+
var noTypeDeclarations = defineRule31({
|
|
1923
3467
|
meta: {
|
|
1924
3468
|
type: "problem",
|
|
1925
3469
|
schema: [{
|
|
@@ -1934,28 +3478,31 @@ var noTypeDeclarations = defineRule22({
|
|
|
1934
3478
|
}
|
|
1935
3479
|
},
|
|
1936
3480
|
createOnce(context) {
|
|
3481
|
+
let allowed = null;
|
|
3482
|
+
function check(node) {
|
|
3483
|
+
if (allowed?.test(node.id.name)) {
|
|
3484
|
+
return;
|
|
3485
|
+
}
|
|
3486
|
+
context.report({
|
|
3487
|
+
node,
|
|
3488
|
+
messageId: "typeDeclaration",
|
|
3489
|
+
data: { name: node.id.name }
|
|
3490
|
+
});
|
|
3491
|
+
}
|
|
1937
3492
|
return {
|
|
1938
|
-
|
|
3493
|
+
before() {
|
|
1939
3494
|
const { allowPattern } = optionsFirst(context, {});
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
}
|
|
1945
|
-
context.report({
|
|
1946
|
-
node: item.node,
|
|
1947
|
-
messageId: "typeDeclaration",
|
|
1948
|
-
data: { name: item.name }
|
|
1949
|
-
});
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
3495
|
+
allowed = allowPattern ? new RegExp(allowPattern) : null;
|
|
3496
|
+
},
|
|
3497
|
+
TSTypeAliasDeclaration: check,
|
|
3498
|
+
TSInterfaceDeclaration: check
|
|
1952
3499
|
};
|
|
1953
3500
|
}
|
|
1954
3501
|
});
|
|
1955
3502
|
|
|
1956
3503
|
// src/rules/no-unescaped-like.ts
|
|
1957
|
-
import { defineRule as
|
|
1958
|
-
var noUnescapedLike =
|
|
3504
|
+
import { defineRule as defineRule32 } from "@oxlint/plugins";
|
|
3505
|
+
var noUnescapedLike = defineRule32({
|
|
1959
3506
|
meta: {
|
|
1960
3507
|
type: "problem",
|
|
1961
3508
|
schema: [{
|
|
@@ -2000,31 +3547,150 @@ var noUnescapedLike = defineRule23({
|
|
|
2000
3547
|
}
|
|
2001
3548
|
});
|
|
2002
3549
|
|
|
3550
|
+
// src/rules/object-multiline.ts
|
|
3551
|
+
import { defineRule as defineRule33 } from "@oxlint/plugins";
|
|
3552
|
+
function objectMultilineIsCallArg(node) {
|
|
3553
|
+
const parent = node.parent;
|
|
3554
|
+
return (parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node);
|
|
3555
|
+
}
|
|
3556
|
+
function objectMultilineIsWhitespace(text) {
|
|
3557
|
+
return /^[\t\n\r\f ]*$/.test(text);
|
|
3558
|
+
}
|
|
3559
|
+
function objectMultilineGaps(text, node) {
|
|
3560
|
+
const gaps = [];
|
|
3561
|
+
const close = node.range[1] - 1;
|
|
3562
|
+
const first = node.properties[0];
|
|
3563
|
+
if (!objectMultilineIsWhitespace(text.slice(node.range[0] + 1, first.range[0]))) {
|
|
3564
|
+
return null;
|
|
3565
|
+
}
|
|
3566
|
+
gaps.push([node.range[0] + 1, first.range[0]]);
|
|
3567
|
+
for (let index = 0;index < node.properties.length; index++) {
|
|
3568
|
+
const property = node.properties[index];
|
|
3569
|
+
const next = node.properties[index + 1];
|
|
3570
|
+
const after = property.range[1];
|
|
3571
|
+
const limit = next ? next.range[0] : close;
|
|
3572
|
+
const between = text.slice(after, limit);
|
|
3573
|
+
const comma = between.indexOf(",");
|
|
3574
|
+
if (comma === -1) {
|
|
3575
|
+
if (next || !objectMultilineIsWhitespace(between)) {
|
|
3576
|
+
return null;
|
|
3577
|
+
}
|
|
3578
|
+
gaps.push([after, close]);
|
|
3579
|
+
continue;
|
|
3580
|
+
}
|
|
3581
|
+
if (!objectMultilineIsWhitespace(between.slice(0, comma)) || !objectMultilineIsWhitespace(between.slice(comma + 1))) {
|
|
3582
|
+
return null;
|
|
3583
|
+
}
|
|
3584
|
+
gaps.push([after + comma + 1, limit]);
|
|
3585
|
+
}
|
|
3586
|
+
return gaps;
|
|
3587
|
+
}
|
|
3588
|
+
var objectMultiline = defineRule33({
|
|
3589
|
+
meta: {
|
|
3590
|
+
type: "layout",
|
|
3591
|
+
fixable: "whitespace",
|
|
3592
|
+
schema: [{
|
|
3593
|
+
type: "object",
|
|
3594
|
+
additionalProperties: false,
|
|
3595
|
+
properties: {
|
|
3596
|
+
minProperties: { type: "integer", minimum: 1, default: 3 },
|
|
3597
|
+
scope: { type: "string", enum: ["call-args", "all"], default: "call-args" },
|
|
3598
|
+
indent: { ...layoutIndentSchema, default: 2 }
|
|
3599
|
+
}
|
|
3600
|
+
}],
|
|
3601
|
+
messages: {
|
|
3602
|
+
callArgs: "Object with {{min}}+ properties in a call should have each property on its own line.",
|
|
3603
|
+
always: "Object with {{min}}+ properties should have each property on its own line."
|
|
3604
|
+
}
|
|
3605
|
+
},
|
|
3606
|
+
createOnce(context) {
|
|
3607
|
+
let minProperties = 3;
|
|
3608
|
+
let scope = "call-args";
|
|
3609
|
+
let indentUnit = layoutIndentUnit();
|
|
3610
|
+
let newline = `
|
|
3611
|
+
`;
|
|
3612
|
+
return {
|
|
3613
|
+
before() {
|
|
3614
|
+
const options = optionsFirst(context, {});
|
|
3615
|
+
minProperties = options.minProperties ?? 3;
|
|
3616
|
+
scope = options.scope ?? "call-args";
|
|
3617
|
+
indentUnit = layoutIndentUnit(options.indent);
|
|
3618
|
+
newline = layoutNewline(context.sourceCode.text);
|
|
3619
|
+
},
|
|
3620
|
+
ObjectExpression(node) {
|
|
3621
|
+
if (node.properties.length < minProperties || node.loc.start.line !== node.loc.end.line) {
|
|
3622
|
+
return;
|
|
3623
|
+
}
|
|
3624
|
+
if (scope === "call-args" && !objectMultilineIsCallArg(node)) {
|
|
3625
|
+
return;
|
|
3626
|
+
}
|
|
3627
|
+
const source = context.sourceCode;
|
|
3628
|
+
const gaps = source.getCommentsInside(node).length > 0 ? null : objectMultilineGaps(source.text, node);
|
|
3629
|
+
const baseIndent = layoutLineIndent(source.text, node.range[0]);
|
|
3630
|
+
const propIndent = `${baseIndent}${indentUnit}`;
|
|
3631
|
+
context.report({
|
|
3632
|
+
node,
|
|
3633
|
+
messageId: scope === "all" ? "always" : "callArgs",
|
|
3634
|
+
data: { min: String(minProperties) },
|
|
3635
|
+
fix(fixer) {
|
|
3636
|
+
if (!gaps) {
|
|
3637
|
+
return null;
|
|
3638
|
+
}
|
|
3639
|
+
return gaps.map((gap, index) => fixer.replaceTextRange(gap, `${newline}${index === gaps.length - 1 ? baseIndent : propIndent}`));
|
|
3640
|
+
}
|
|
3641
|
+
});
|
|
3642
|
+
}
|
|
3643
|
+
};
|
|
3644
|
+
}
|
|
3645
|
+
});
|
|
3646
|
+
|
|
2003
3647
|
// src/rules/only-export-components.ts
|
|
2004
|
-
import { defineRule as
|
|
2005
|
-
var onlyExportComponents =
|
|
3648
|
+
import { defineRule as defineRule34 } from "@oxlint/plugins";
|
|
3649
|
+
var onlyExportComponents = defineRule34({
|
|
2006
3650
|
meta: {
|
|
2007
3651
|
type: "problem",
|
|
2008
3652
|
schema: [{
|
|
2009
3653
|
type: "object",
|
|
2010
3654
|
additionalProperties: false,
|
|
2011
3655
|
properties: {
|
|
2012
|
-
matchFileName: { type: "boolean" }
|
|
3656
|
+
matchFileName: { type: "boolean" },
|
|
3657
|
+
allowTypeExports: { type: "boolean" },
|
|
3658
|
+
denyTypePattern: { type: "string" }
|
|
2013
3659
|
}
|
|
2014
3660
|
}],
|
|
2015
3661
|
messages: {
|
|
2016
3662
|
nonComponent: "Export '{{name}}' must be a React component or type.",
|
|
2017
|
-
nameMismatch: "Component export '{{name}}' must match '{{expected}}' or its prefix."
|
|
3663
|
+
nameMismatch: "Component export '{{name}}' must match '{{expected}}' or its prefix.",
|
|
3664
|
+
typeExport: "Type export '{{name}}' is not allowed.",
|
|
3665
|
+
deniedType: "Type export '{{name}}' is denied by denyTypePattern."
|
|
2018
3666
|
}
|
|
2019
3667
|
},
|
|
2020
3668
|
createOnce(context) {
|
|
2021
3669
|
return {
|
|
2022
3670
|
Program(program) {
|
|
2023
|
-
const {
|
|
3671
|
+
const {
|
|
3672
|
+
matchFileName = true,
|
|
3673
|
+
allowTypeExports = true,
|
|
3674
|
+
denyTypePattern
|
|
3675
|
+
} = optionsFirst(context, {});
|
|
2024
3676
|
const expected = reactComponentsPascalFromBasename(namingFileBasename(context.filename));
|
|
3677
|
+
const denied = denyTypePattern ? new RegExp(denyTypePattern) : null;
|
|
2025
3678
|
const components = [];
|
|
2026
3679
|
for (const candidate of reactComponentsExportCandidates(program)) {
|
|
2027
3680
|
if (candidate.typeOnly) {
|
|
3681
|
+
if (!allowTypeExports) {
|
|
3682
|
+
context.report({
|
|
3683
|
+
node: candidate.node,
|
|
3684
|
+
messageId: "typeExport",
|
|
3685
|
+
data: { name: candidate.name, expected }
|
|
3686
|
+
});
|
|
3687
|
+
} else if (denied?.test(candidate.name)) {
|
|
3688
|
+
context.report({
|
|
3689
|
+
node: candidate.node,
|
|
3690
|
+
messageId: "deniedType",
|
|
3691
|
+
data: { name: candidate.name, expected }
|
|
3692
|
+
});
|
|
3693
|
+
}
|
|
2028
3694
|
continue;
|
|
2029
3695
|
}
|
|
2030
3696
|
if (!reactComponentsIsLike(program, candidate)) {
|
|
@@ -2058,7 +3724,7 @@ var onlyExportComponents = defineRule24({
|
|
|
2058
3724
|
});
|
|
2059
3725
|
|
|
2060
3726
|
// src/rules/only-export-constants.ts
|
|
2061
|
-
import { defineRule as
|
|
3727
|
+
import { defineRule as defineRule35 } from "@oxlint/plugins";
|
|
2062
3728
|
function bindingNames(pattern) {
|
|
2063
3729
|
switch (pattern.type) {
|
|
2064
3730
|
case "Identifier":
|
|
@@ -2113,7 +3779,7 @@ function collectBindings(program) {
|
|
|
2113
3779
|
}
|
|
2114
3780
|
return bindings;
|
|
2115
3781
|
}
|
|
2116
|
-
var onlyExportConstants =
|
|
3782
|
+
var onlyExportConstants = defineRule35({
|
|
2117
3783
|
meta: {
|
|
2118
3784
|
type: "problem",
|
|
2119
3785
|
schema: [{
|
|
@@ -2194,9 +3860,128 @@ var onlyExportConstants = defineRule25({
|
|
|
2194
3860
|
}
|
|
2195
3861
|
});
|
|
2196
3862
|
|
|
3863
|
+
// src/rules/padding-between-statements.ts
|
|
3864
|
+
import { defineRule as defineRule36 } from "@oxlint/plugins";
|
|
3865
|
+
function paddingKind(node) {
|
|
3866
|
+
if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
|
|
3867
|
+
return node.declaration ? paddingKind(node.declaration) : "other";
|
|
3868
|
+
}
|
|
3869
|
+
switch (node.type) {
|
|
3870
|
+
case "VariableDeclaration":
|
|
3871
|
+
return "variable";
|
|
3872
|
+
case "FunctionDeclaration":
|
|
3873
|
+
case "ClassDeclaration":
|
|
3874
|
+
return "block";
|
|
3875
|
+
case "IfStatement":
|
|
3876
|
+
case "ForStatement":
|
|
3877
|
+
case "ForInStatement":
|
|
3878
|
+
case "ForOfStatement":
|
|
3879
|
+
case "WhileStatement":
|
|
3880
|
+
case "DoWhileStatement":
|
|
3881
|
+
case "SwitchStatement":
|
|
3882
|
+
case "TryStatement":
|
|
3883
|
+
return "control";
|
|
3884
|
+
case "ReturnStatement":
|
|
3885
|
+
return "return";
|
|
3886
|
+
default:
|
|
3887
|
+
return "other";
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
function paddingHasBlankLine(gap) {
|
|
3891
|
+
const lines = gap.split(`
|
|
3892
|
+
`);
|
|
3893
|
+
for (let index = 1;index < lines.length - 1; index++) {
|
|
3894
|
+
if (/^[\t\r ]*$/.test(lines[index])) {
|
|
3895
|
+
return true;
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3898
|
+
return false;
|
|
3899
|
+
}
|
|
3900
|
+
function paddingBlankLineFix(sourceText, comments, prev, curr, newline) {
|
|
3901
|
+
const gap = sourceText.slice(prev.range[1], curr.range[0]);
|
|
3902
|
+
const match = /\r?\n/.exec(gap);
|
|
3903
|
+
if (!match) {
|
|
3904
|
+
return null;
|
|
3905
|
+
}
|
|
3906
|
+
const newlineStart = prev.range[1] + match.index;
|
|
3907
|
+
const newlineEnd = newlineStart + match[0].length;
|
|
3908
|
+
if (comments.some((comment) => comment.range[0] < newlineEnd && comment.range[1] > newlineStart)) {
|
|
3909
|
+
return null;
|
|
3910
|
+
}
|
|
3911
|
+
if (comments.some((comment) => comment.loc.start.line === prev.loc.end.line && /^\s*[A-Za-z][\w-]*-disable-next-line\b/.test(commentsNormalizedValue(comment)))) {
|
|
3912
|
+
return null;
|
|
3913
|
+
}
|
|
3914
|
+
return { newlineEnd, newline };
|
|
3915
|
+
}
|
|
3916
|
+
var paddingBetweenStatements = defineRule36({
|
|
3917
|
+
meta: {
|
|
3918
|
+
type: "layout",
|
|
3919
|
+
fixable: "whitespace",
|
|
3920
|
+
schema: [{
|
|
3921
|
+
type: "object",
|
|
3922
|
+
additionalProperties: false,
|
|
3923
|
+
properties: {
|
|
3924
|
+
returnMinStatements: { type: "integer", minimum: 1, default: 3 }
|
|
3925
|
+
}
|
|
3926
|
+
}],
|
|
3927
|
+
messages: {
|
|
3928
|
+
variableAndBlock: "Expected a blank line between a variable declaration and a function/class.",
|
|
3929
|
+
beforeBlock: "Expected a blank line before a function/class declaration.",
|
|
3930
|
+
afterBlock: "Expected a blank line after a function/class declaration.",
|
|
3931
|
+
variableAndControl: "Expected a blank line between a variable declaration and control flow.",
|
|
3932
|
+
controlAndVariable: "Expected a blank line between control flow and a variable declaration.",
|
|
3933
|
+
consecutiveControl: "Expected a blank line between control flow statements.",
|
|
3934
|
+
beforeReturn: "Expected a blank line before return statement."
|
|
3935
|
+
}
|
|
3936
|
+
},
|
|
3937
|
+
createOnce(context) {
|
|
3938
|
+
let returnMinStatements = 3;
|
|
3939
|
+
let newline = `
|
|
3940
|
+
`;
|
|
3941
|
+
let source;
|
|
3942
|
+
function checkStatements(statements) {
|
|
3943
|
+
for (let index = 1;index < statements.length; index++) {
|
|
3944
|
+
const prev = statements[index - 1];
|
|
3945
|
+
const curr = statements[index];
|
|
3946
|
+
const gap = source.text.slice(prev.range[1], curr.range[0]);
|
|
3947
|
+
if (!/\r?\n/.test(gap) || paddingHasBlankLine(gap)) {
|
|
3948
|
+
continue;
|
|
3949
|
+
}
|
|
3950
|
+
const prevKind = paddingKind(prev);
|
|
3951
|
+
const currKind = paddingKind(curr);
|
|
3952
|
+
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;
|
|
3953
|
+
if (!messageId) {
|
|
3954
|
+
continue;
|
|
3955
|
+
}
|
|
3956
|
+
const comments = source.getCommentsAfter(prev).filter((comment) => comment.range[1] <= curr.range[0]);
|
|
3957
|
+
const insert = paddingBlankLineFix(source.text, comments, prev, curr, newline);
|
|
3958
|
+
context.report({
|
|
3959
|
+
node: curr,
|
|
3960
|
+
messageId,
|
|
3961
|
+
fix: insert ? (fixer) => fixer.insertTextAfterRange([insert.newlineEnd, insert.newlineEnd], insert.newline) : undefined
|
|
3962
|
+
});
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
return {
|
|
3966
|
+
before() {
|
|
3967
|
+
const options = optionsFirst(context, {});
|
|
3968
|
+
returnMinStatements = options.returnMinStatements ?? 3;
|
|
3969
|
+
source = context.sourceCode;
|
|
3970
|
+
newline = layoutNewline(source.text);
|
|
3971
|
+
},
|
|
3972
|
+
Program(node) {
|
|
3973
|
+
checkStatements(node.body);
|
|
3974
|
+
},
|
|
3975
|
+
BlockStatement(node) {
|
|
3976
|
+
checkStatements(node.body);
|
|
3977
|
+
}
|
|
3978
|
+
};
|
|
3979
|
+
}
|
|
3980
|
+
});
|
|
3981
|
+
|
|
2197
3982
|
// src/rules/require-file-factory.ts
|
|
2198
|
-
import { defineRule as
|
|
2199
|
-
var requireFileFactory =
|
|
3983
|
+
import { defineRule as defineRule37 } from "@oxlint/plugins";
|
|
3984
|
+
var requireFileFactory = defineRule37({
|
|
2200
3985
|
meta: {
|
|
2201
3986
|
type: "problem",
|
|
2202
3987
|
schema: [{
|
|
@@ -2242,24 +4027,46 @@ var requireFileFactory = defineRule26({
|
|
|
2242
4027
|
});
|
|
2243
4028
|
|
|
2244
4029
|
// src/rules/require-object-params.ts
|
|
2245
|
-
import { defineRule as
|
|
2246
|
-
var requireObjectParams =
|
|
4030
|
+
import { defineRule as defineRule38 } from "@oxlint/plugins";
|
|
4031
|
+
var requireObjectParams = defineRule38({
|
|
2247
4032
|
meta: {
|
|
2248
4033
|
type: "problem",
|
|
2249
|
-
schema: [
|
|
4034
|
+
schema: [{
|
|
4035
|
+
type: "object",
|
|
4036
|
+
additionalProperties: false,
|
|
4037
|
+
properties: {
|
|
4038
|
+
maxParams: { type: "integer", minimum: 1 },
|
|
4039
|
+
allDeclarations: { type: "boolean" },
|
|
4040
|
+
allowPattern: { type: "string" }
|
|
4041
|
+
}
|
|
4042
|
+
}],
|
|
2250
4043
|
messages: {
|
|
2251
|
-
objectParams: "
|
|
4044
|
+
objectParams: "{{label}} {{name}} must accept at most {{maxParams}} object parameter{{s}}."
|
|
2252
4045
|
}
|
|
2253
4046
|
},
|
|
2254
4047
|
createOnce(context) {
|
|
2255
4048
|
return {
|
|
2256
4049
|
Program(program) {
|
|
2257
|
-
|
|
2258
|
-
|
|
4050
|
+
const { maxParams = 1, allDeclarations = false, allowPattern } = optionsFirst(context, {});
|
|
4051
|
+
const allowed = allowPattern ? new RegExp(allowPattern) : null;
|
|
4052
|
+
const seen = new Set;
|
|
4053
|
+
const functions = allDeclarations ? [...exportsCollectFunctions(program), ...declarationsTopLevelUnexportedFunctions(program)] : exportsCollectFunctions(program);
|
|
4054
|
+
for (const item of functions) {
|
|
4055
|
+
const key = `${item.name}:${item.node.start}:${item.node.end}`;
|
|
4056
|
+
if (seen.has(key) || allowed?.test(item.name)) {
|
|
4057
|
+
continue;
|
|
4058
|
+
}
|
|
4059
|
+
seen.add(key);
|
|
4060
|
+
if (item.node.params.length > maxParams || item.node.params.some((param) => !declarationsIsObjectParam(param))) {
|
|
2259
4061
|
context.report({
|
|
2260
4062
|
node: item.node,
|
|
2261
4063
|
messageId: "objectParams",
|
|
2262
|
-
data: {
|
|
4064
|
+
data: {
|
|
4065
|
+
label: allDeclarations ? "Function" : "Exported function",
|
|
4066
|
+
name: item.name,
|
|
4067
|
+
maxParams: maxParams === 1 ? "one" : String(maxParams),
|
|
4068
|
+
s: maxParams === 1 ? "" : "s"
|
|
4069
|
+
}
|
|
2263
4070
|
});
|
|
2264
4071
|
}
|
|
2265
4072
|
}
|
|
@@ -2269,8 +4076,8 @@ var requireObjectParams = defineRule27({
|
|
|
2269
4076
|
});
|
|
2270
4077
|
|
|
2271
4078
|
// src/rules/require-orpc-output.ts
|
|
2272
|
-
import { defineRule as
|
|
2273
|
-
var requireOrpcOutput =
|
|
4079
|
+
import { defineRule as defineRule39 } from "@oxlint/plugins";
|
|
4080
|
+
var requireOrpcOutput = defineRule39({
|
|
2274
4081
|
meta: {
|
|
2275
4082
|
type: "problem",
|
|
2276
4083
|
schema: [{
|
|
@@ -2312,8 +4119,8 @@ var requireOrpcOutput = defineRule28({
|
|
|
2312
4119
|
});
|
|
2313
4120
|
|
|
2314
4121
|
// src/rules/require-paired-call.ts
|
|
2315
|
-
import { defineRule as
|
|
2316
|
-
var requirePairedCall =
|
|
4122
|
+
import { defineRule as defineRule40 } from "@oxlint/plugins";
|
|
4123
|
+
var requirePairedCall = defineRule40({
|
|
2317
4124
|
meta: {
|
|
2318
4125
|
type: "problem",
|
|
2319
4126
|
schema: [{
|
|
@@ -2321,9 +4128,24 @@ var requirePairedCall = defineRule29({
|
|
|
2321
4128
|
additionalProperties: false,
|
|
2322
4129
|
properties: {
|
|
2323
4130
|
when: { type: "string" },
|
|
2324
|
-
require: { type: "string" }
|
|
4131
|
+
require: { type: "string" },
|
|
4132
|
+
pairs: {
|
|
4133
|
+
type: "array",
|
|
4134
|
+
items: {
|
|
4135
|
+
type: "object",
|
|
4136
|
+
additionalProperties: false,
|
|
4137
|
+
properties: {
|
|
4138
|
+
when: { type: "string" },
|
|
4139
|
+
require: { type: "string" }
|
|
4140
|
+
},
|
|
4141
|
+
required: ["when", "require"]
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
2325
4144
|
},
|
|
2326
|
-
|
|
4145
|
+
anyOf: [
|
|
4146
|
+
{ required: ["when", "require"] },
|
|
4147
|
+
{ required: ["pairs"] }
|
|
4148
|
+
]
|
|
2327
4149
|
}],
|
|
2328
4150
|
messages: {
|
|
2329
4151
|
paired: "{{when}} requires a call to {{require}} in the same file."
|
|
@@ -2331,45 +4153,50 @@ var requirePairedCall = defineRule29({
|
|
|
2331
4153
|
},
|
|
2332
4154
|
createOnce(context) {
|
|
2333
4155
|
let aliases = new Map;
|
|
2334
|
-
let
|
|
2335
|
-
let sawRequired = false;
|
|
4156
|
+
let pairs = [];
|
|
2336
4157
|
return {
|
|
2337
4158
|
before() {
|
|
4159
|
+
const options = optionsFirst(context);
|
|
4160
|
+
const configured = [
|
|
4161
|
+
...options.when && options.require ? [{ when: options.when, require: options.require }] : [],
|
|
4162
|
+
...options.pairs ?? []
|
|
4163
|
+
];
|
|
2338
4164
|
aliases = new Map;
|
|
2339
|
-
|
|
2340
|
-
sawRequired = false;
|
|
4165
|
+
pairs = configured.map((pair) => ({ ...pair, firstWhen: null, sawRequired: false }));
|
|
2341
4166
|
},
|
|
2342
4167
|
Program(program) {
|
|
2343
4168
|
aliases = astImportedCallAliases(program);
|
|
2344
4169
|
},
|
|
2345
4170
|
CallExpression(node) {
|
|
2346
|
-
const { when, require: require2 } = optionsFirst(context);
|
|
2347
4171
|
const name = astResolvedCallName(node, aliases);
|
|
2348
|
-
|
|
2349
|
-
firstWhen
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
4172
|
+
for (const pair of pairs) {
|
|
4173
|
+
if (name === pair.when && !pair.firstWhen) {
|
|
4174
|
+
pair.firstWhen = node;
|
|
4175
|
+
}
|
|
4176
|
+
if (name === pair.require) {
|
|
4177
|
+
pair.sawRequired = true;
|
|
4178
|
+
}
|
|
2353
4179
|
}
|
|
2354
4180
|
},
|
|
2355
4181
|
after() {
|
|
2356
|
-
|
|
2357
|
-
|
|
4182
|
+
for (const pair of pairs) {
|
|
4183
|
+
if (!pair.firstWhen || pair.sawRequired) {
|
|
4184
|
+
continue;
|
|
4185
|
+
}
|
|
4186
|
+
context.report({
|
|
4187
|
+
node: pair.firstWhen,
|
|
4188
|
+
messageId: "paired",
|
|
4189
|
+
data: { when: pair.when, require: pair.require }
|
|
4190
|
+
});
|
|
2358
4191
|
}
|
|
2359
|
-
const { when, require: require2 } = optionsFirst(context);
|
|
2360
|
-
context.report({
|
|
2361
|
-
node: firstWhen,
|
|
2362
|
-
messageId: "paired",
|
|
2363
|
-
data: { when, require: require2 }
|
|
2364
|
-
});
|
|
2365
4192
|
}
|
|
2366
4193
|
};
|
|
2367
4194
|
}
|
|
2368
4195
|
});
|
|
2369
4196
|
|
|
2370
4197
|
// src/rules/route-surface.ts
|
|
2371
|
-
import { defineRule as
|
|
2372
|
-
var routeSurface =
|
|
4198
|
+
import { defineRule as defineRule41 } from "@oxlint/plugins";
|
|
4199
|
+
var routeSurface = defineRule41({
|
|
2373
4200
|
meta: {
|
|
2374
4201
|
type: "problem",
|
|
2375
4202
|
schema: [{
|
|
@@ -2410,25 +4237,140 @@ var routeSurface = defineRule30({
|
|
|
2410
4237
|
}
|
|
2411
4238
|
});
|
|
2412
4239
|
|
|
4240
|
+
// src/rules/test-title-pattern.ts
|
|
4241
|
+
import { defineRule as defineRule42 } from "@oxlint/plugins";
|
|
4242
|
+
var TEST_MODIFIERS = new Set(["only", "skip", "concurrent", "todo", "each"]);
|
|
4243
|
+
function isConfiguredName(name, callees) {
|
|
4244
|
+
if (!name) {
|
|
4245
|
+
return false;
|
|
4246
|
+
}
|
|
4247
|
+
return callees.some((callee) => {
|
|
4248
|
+
if (name === callee) {
|
|
4249
|
+
return true;
|
|
4250
|
+
}
|
|
4251
|
+
if (!name.startsWith(`${callee}.`)) {
|
|
4252
|
+
return false;
|
|
4253
|
+
}
|
|
4254
|
+
return name.slice(callee.length + 1).split(".").every((part) => TEST_MODIFIERS.has(part));
|
|
4255
|
+
});
|
|
4256
|
+
}
|
|
4257
|
+
function isConfiguredCall(node, callees) {
|
|
4258
|
+
if (isConfiguredName(astDottedName(node.callee), callees)) {
|
|
4259
|
+
return true;
|
|
4260
|
+
}
|
|
4261
|
+
if (node.callee.type === "CallExpression") {
|
|
4262
|
+
return isConfiguredName(astDottedName(node.callee.callee), callees);
|
|
4263
|
+
}
|
|
4264
|
+
return node.callee.type === "TaggedTemplateExpression" && isConfiguredName(astDottedName(node.callee.tag), callees);
|
|
4265
|
+
}
|
|
4266
|
+
function titleText(node) {
|
|
4267
|
+
if (node.type === "Literal" && typeof node.value === "string") {
|
|
4268
|
+
return node.value;
|
|
4269
|
+
}
|
|
4270
|
+
if (node.type === "TemplateLiteral" && node.expressions.length === 0) {
|
|
4271
|
+
return node.quasis[0]?.value.cooked ?? node.quasis[0]?.value.raw;
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
function regexMatches(regex, value) {
|
|
4275
|
+
regex.lastIndex = 0;
|
|
4276
|
+
return regex.test(value);
|
|
4277
|
+
}
|
|
4278
|
+
var testTitlePattern = defineRule42({
|
|
4279
|
+
meta: {
|
|
4280
|
+
type: "suggestion",
|
|
4281
|
+
schema: [{
|
|
4282
|
+
type: "object",
|
|
4283
|
+
additionalProperties: false,
|
|
4284
|
+
properties: {
|
|
4285
|
+
callees: { type: "array", items: { type: "string" } },
|
|
4286
|
+
forbid: { type: "string" },
|
|
4287
|
+
require: { type: "string" },
|
|
4288
|
+
flags: { type: "string", pattern: "^[dgimsuvy]*$" }
|
|
4289
|
+
}
|
|
4290
|
+
}],
|
|
4291
|
+
defaultOptions: [{
|
|
4292
|
+
callees: ["describe", "it", "test"],
|
|
4293
|
+
flags: ""
|
|
4294
|
+
}],
|
|
4295
|
+
messages: {
|
|
4296
|
+
forbidden: "Test title matches forbidden pattern '{{pattern}}'.",
|
|
4297
|
+
required: "Test title must match required pattern '{{pattern}}'."
|
|
4298
|
+
}
|
|
4299
|
+
},
|
|
4300
|
+
createOnce(context) {
|
|
4301
|
+
let callees = [];
|
|
4302
|
+
let forbid;
|
|
4303
|
+
let required;
|
|
4304
|
+
return {
|
|
4305
|
+
before() {
|
|
4306
|
+
const options = optionsFirst(context);
|
|
4307
|
+
callees = options.callees;
|
|
4308
|
+
const flags = options.flags ?? "";
|
|
4309
|
+
forbid = options.forbid === undefined ? undefined : { regex: new RegExp(options.forbid, flags), pattern: options.forbid };
|
|
4310
|
+
required = options.require === undefined ? undefined : { regex: new RegExp(options.require, flags), pattern: options.require };
|
|
4311
|
+
if (forbid === undefined && required === undefined) {
|
|
4312
|
+
return false;
|
|
4313
|
+
}
|
|
4314
|
+
},
|
|
4315
|
+
CallExpression(node) {
|
|
4316
|
+
if (!isConfiguredCall(node, callees)) {
|
|
4317
|
+
return;
|
|
4318
|
+
}
|
|
4319
|
+
const title = node.arguments[0];
|
|
4320
|
+
if (!title) {
|
|
4321
|
+
return;
|
|
4322
|
+
}
|
|
4323
|
+
const text = titleText(title);
|
|
4324
|
+
if (text === undefined) {
|
|
4325
|
+
return;
|
|
4326
|
+
}
|
|
4327
|
+
if (forbid && regexMatches(forbid.regex, text)) {
|
|
4328
|
+
context.report({
|
|
4329
|
+
node: title,
|
|
4330
|
+
messageId: "forbidden",
|
|
4331
|
+
data: { pattern: forbid.pattern }
|
|
4332
|
+
});
|
|
4333
|
+
}
|
|
4334
|
+
if (required && !regexMatches(required.regex, text)) {
|
|
4335
|
+
context.report({
|
|
4336
|
+
node: title,
|
|
4337
|
+
messageId: "required",
|
|
4338
|
+
data: { pattern: required.pattern }
|
|
4339
|
+
});
|
|
4340
|
+
}
|
|
4341
|
+
}
|
|
4342
|
+
};
|
|
4343
|
+
}
|
|
4344
|
+
});
|
|
4345
|
+
|
|
2413
4346
|
// src/index.ts
|
|
2414
4347
|
var src_default = eslintCompatPlugin({
|
|
2415
4348
|
meta: { name: "arch" },
|
|
2416
4349
|
rules: {
|
|
4350
|
+
"call-array-multiline": callArrayMultiline,
|
|
4351
|
+
"chain-newline": chainNewline,
|
|
2417
4352
|
"declaration-name": declarationName,
|
|
2418
4353
|
"export-file-prefix": exportFilePrefix,
|
|
2419
4354
|
"export-name-pattern": exportNamePattern,
|
|
2420
4355
|
"filename-export-name": filenameExportName,
|
|
2421
4356
|
"filename-match": filenameMatch,
|
|
2422
4357
|
"folder-prefix": folderPrefix,
|
|
4358
|
+
"jsx-attributes-multiline": jsxAttributesMultiline,
|
|
4359
|
+
"key-value-same-line": keyValueSameLine,
|
|
4360
|
+
"no-comments": noComments,
|
|
2423
4361
|
"no-extra-exports": noExtraExports,
|
|
2424
4362
|
"no-extra-factory-keys": noExtraFactoryKeys,
|
|
2425
4363
|
"no-file-level-helpers": noFileLevelHelpers,
|
|
2426
4364
|
"no-imported-type-alias": noImportedTypeAlias,
|
|
2427
4365
|
"no-inline-schema-elements": noInlineSchemaElements,
|
|
2428
4366
|
"no-inline-types": noInlineTypes,
|
|
4367
|
+
"no-literal-in": noLiteralIn,
|
|
2429
4368
|
"no-local-schema-construction": noLocalSchemaConstruction,
|
|
2430
4369
|
"no-member-comments": noMemberComments,
|
|
4370
|
+
"no-module-mutable-state": noModuleMutableState,
|
|
4371
|
+
"no-promise-all-mutation": noPromiseAllMutation,
|
|
2431
4372
|
"no-rederive-schema": noRederiveSchema,
|
|
4373
|
+
"no-restricted-constructor": noRestrictedConstructor,
|
|
2432
4374
|
"no-restricted-files": noRestrictedFiles,
|
|
2433
4375
|
"no-restricted-token": noRestrictedToken,
|
|
2434
4376
|
"no-runtime-in-types": noRuntimeInTypes,
|
|
@@ -2437,13 +4379,16 @@ var src_default = eslintCompatPlugin({
|
|
|
2437
4379
|
"no-trivial-functions": noTrivialFunctions,
|
|
2438
4380
|
"no-type-declarations": noTypeDeclarations,
|
|
2439
4381
|
"no-unescaped-like": noUnescapedLike,
|
|
4382
|
+
"object-multiline": objectMultiline,
|
|
2440
4383
|
"only-export-components": onlyExportComponents,
|
|
2441
4384
|
"only-export-constants": onlyExportConstants,
|
|
4385
|
+
"padding-between-statements": paddingBetweenStatements,
|
|
2442
4386
|
"require-file-factory": requireFileFactory,
|
|
2443
4387
|
"require-object-params": requireObjectParams,
|
|
2444
4388
|
"require-orpc-output": requireOrpcOutput,
|
|
2445
4389
|
"require-paired-call": requirePairedCall,
|
|
2446
|
-
"route-surface": routeSurface
|
|
4390
|
+
"route-surface": routeSurface,
|
|
4391
|
+
"test-title-pattern": testTitlePattern
|
|
2447
4392
|
}
|
|
2448
4393
|
});
|
|
2449
4394
|
export {
|