@deslop/coding-standards 0.1.1082 → 0.1.1085
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/README.md +12 -8
- package/dist/{MutableList-BCF17jPH.mjs → MutableList-NTBI6AHW.mjs} +231 -8
- package/dist/install.js +35 -80
- package/dist/oxlint.d.ts +86 -4
- package/dist/oxlint.js +837 -147
- package/package.json +1 -1
- package/skills/design/SKILL.md +52 -129
- package/skills/engineering/SKILL.md +193 -128
- package/skills/review/SKILL.md +37 -35
- package/skills/architecture/EVALS.md +0 -21
- package/skills/architecture/SKILL.md +0 -39
- package/skills/design/EVALS.md +0 -46
- package/skills/engineering/EVALS.md +0 -34
- package/skills/review/EVALS.md +0 -24
- package/skills/testing/EVALS.md +0 -22
- package/skills/testing/SKILL.md +0 -64
package/dist/oxlint.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $ as
|
|
1
|
+
import { $ as last, $r as pipeArguments, A as dedupe, Ar as isNullish$1, At as filter, Cr as isBoolean, D as appendAll, Dr as isNotNull, Dt as all, E as append, F as every, Fr as isReadonlyObject, Ft as fromUndefinedOr, G as get$1, Gt as some, H as flatten, Ht as match, I as filter$1, Ir as isRegExp, It as getOrElse, J as isArrayEmpty, K as getSomes, Kr as dual, Kt as toArray$1, L as filterMap, Lr as isString, Lt as getOrUndefined, M as dropRight, Mt as flatMap, N as empty, Nt as flatten$1, O as contains, Or as isNotNullish, Ot as contains$1, P as ensure, Pt as fromNullishOr, Q as join, Qn as NodeInspectSymbol, R as findFirst, Rt as isNone, U as forEach, Ut as none, V as flatMap$1, Vr as constFalse, Vt as map, Wr as constVoid, Wt as orElse, Xr as pipe, Y as isArrayNonEmpty, Z as isReadonlyArrayNonEmpty, _ as startsWith, _r as string, at as prepend, br as symbol$1, c as make$3, ct as sort, d as Order, dr as symbol, dt as fromEntries, f as includes, ft as get$2, g as split, gr as optimize, gt as values, h as replaceAll, hr as number, ht as remove, it as of, j as dedupeWith, jr as isNumber, jt as firstSomeOf, k as containsWith, kt as exists, l as update, lr as byReferenceUnsafe, lt as take, m as replace, mr as hash, o as toArray, on as strictEqual, p as match$1, pr as combine, q as head, qt as zipWith, r as make$2, rt as matchRight, s as get$3, st as some$1, t as append$1, tt as map$1, u as match$2, ur as equals, ut as takeWhile, v as toUpperCase, w as TaggedError, x as parse, xr as hasProperty, y as trim, yt as fromOption, z as findFirstIndex, zt as isSome } from "./MutableList-NTBI6AHW.mjs";
|
|
2
2
|
//#region ../../node_modules/.pnpm/effect@4.0.5/node_modules/effect/dist/internal/graph.js
|
|
3
3
|
/** @internal */
|
|
4
4
|
const TypeId = "~effect/Graph";
|
|
@@ -633,6 +633,28 @@ function defineConfig(config) {
|
|
|
633
633
|
}
|
|
634
634
|
//#endregion
|
|
635
635
|
//#region src/rules/shared.ts
|
|
636
|
+
function inert(token) {
|
|
637
|
+
if (contains([
|
|
638
|
+
"Boolean",
|
|
639
|
+
"Identifier",
|
|
640
|
+
"Null",
|
|
641
|
+
"Numeric",
|
|
642
|
+
"String"
|
|
643
|
+
], token.type)) return true;
|
|
644
|
+
if (token.type === "Keyword") return contains([
|
|
645
|
+
"const",
|
|
646
|
+
"new",
|
|
647
|
+
"return"
|
|
648
|
+
], token.value);
|
|
649
|
+
return token.type === "Punctuator" && contains([
|
|
650
|
+
"(",
|
|
651
|
+
",",
|
|
652
|
+
":",
|
|
653
|
+
"=",
|
|
654
|
+
"[",
|
|
655
|
+
"{"
|
|
656
|
+
], token.value);
|
|
657
|
+
}
|
|
636
658
|
function variableFromScope(input) {
|
|
637
659
|
if (input.scope === null) return none();
|
|
638
660
|
const scope = input.scope;
|
|
@@ -690,7 +712,7 @@ function importedMember(input) {
|
|
|
690
712
|
importedName: input.importedName,
|
|
691
713
|
node: input.node.object,
|
|
692
714
|
source: "effect"
|
|
693
|
-
}) && (input.propertyName === void 0 || contains(memberName(input.node), input.propertyName));
|
|
715
|
+
}) && (input.propertyName === void 0 || contains$1(memberName(input.node), input.propertyName));
|
|
694
716
|
}
|
|
695
717
|
function matches(regExp) {
|
|
696
718
|
return (text) => isSome(pipe(text, match$1(regExp)));
|
|
@@ -711,7 +733,7 @@ function isReactUseState(input) {
|
|
|
711
733
|
node: input.node.callee,
|
|
712
734
|
source: "react"
|
|
713
735
|
});
|
|
714
|
-
return input.node.callee.type === "MemberExpression" && contains(memberName(input.node.callee), "useState") && input.node.callee.object.type === "Identifier" && isNamespaceImport({
|
|
736
|
+
return input.node.callee.type === "MemberExpression" && contains$1(memberName(input.node.callee), "useState") && input.node.callee.object.type === "Identifier" && isNamespaceImport({
|
|
715
737
|
context: input.context,
|
|
716
738
|
node: input.node.callee.object,
|
|
717
739
|
source: "react"
|
|
@@ -868,7 +890,7 @@ function schemaCycleNames(input) {
|
|
|
868
890
|
const indices = fromEntries(map$1(names, (name) => [name, addNode(mutable, name)]));
|
|
869
891
|
for (const edge of edges) for (const target of edge.targets) zipWith(get$2(indices, edge.name), get$2(indices, target), (source, sink) => addEdge(mutable, source, sink, "reads"));
|
|
870
892
|
});
|
|
871
|
-
return pipe(stronglyConnectedComponents(graph), map$1((component) => getSomes(map$1(component, (index) => getNode(graph, index)))), filter$1((component) => (component.length > 1 || some$1(edges, (edge) => contains
|
|
893
|
+
return pipe(stronglyConnectedComponents(graph), map$1((component) => getSomes(map$1(component, (index) => getNode(graph, index)))), filter$1((component) => (component.length > 1 || some$1(edges, (edge) => contains(component, edge.name) && contains(edge.targets, edge.name))) && some$1(component, (name) => contains(seeds, name))), flatten);
|
|
872
894
|
}
|
|
873
895
|
function hasSingleStatementExpression(node) {
|
|
874
896
|
if (node.body === null) return false;
|
|
@@ -890,9 +912,159 @@ function singleUseThunk(input) {
|
|
|
890
912
|
})), exists((variable) => filter$1(variable.references, (reference) => reference.isRead()).length === 1));
|
|
891
913
|
}
|
|
892
914
|
//#endregion
|
|
915
|
+
//#region src/rules/explicit-effect-concurrency.ts
|
|
916
|
+
function statesConcurrency(node) {
|
|
917
|
+
if (node === void 0) return false;
|
|
918
|
+
if (node.type !== "ObjectExpression") return true;
|
|
919
|
+
return some$1(node.properties, (property) => property.type === "SpreadElement" || property.key.type === "Identifier" && property.key.name === "concurrency" || property.key.type === "Literal" && property.key.value === "concurrency");
|
|
920
|
+
}
|
|
921
|
+
function isFunction(node) {
|
|
922
|
+
return node?.type === "ArrowFunctionExpression" || node?.type === "FunctionExpression";
|
|
923
|
+
}
|
|
924
|
+
const explicitEffectConcurrency = defineRule({
|
|
925
|
+
create: (context) => ({ CallExpression: (node) => {
|
|
926
|
+
if (node.callee.type !== "MemberExpression" || !importedMember({
|
|
927
|
+
context,
|
|
928
|
+
importedName: "Effect",
|
|
929
|
+
node: node.callee
|
|
930
|
+
})) return;
|
|
931
|
+
const name = memberName(node.callee);
|
|
932
|
+
if (!exists(name, (method) => method === "all" || method === "forEach")) return;
|
|
933
|
+
const index = match$2(contains$1(name, "all") || isFunction(node.arguments[0]) || node.arguments[1]?.type === "ObjectExpression", {
|
|
934
|
+
onFalse: () => 2,
|
|
935
|
+
onTrue: () => 1
|
|
936
|
+
});
|
|
937
|
+
if (statesConcurrency(node.arguments[index])) return;
|
|
938
|
+
context.report({
|
|
939
|
+
message: "Choose the concurrency: {concurrency: 'unbounded'} for independent effects, {concurrency: 1} when order or a shared resource matters.",
|
|
940
|
+
node: node.callee
|
|
941
|
+
});
|
|
942
|
+
} }),
|
|
943
|
+
meta: { type: "problem" }
|
|
944
|
+
});
|
|
945
|
+
//#endregion
|
|
946
|
+
//#region src/rules/module-constant.ts
|
|
947
|
+
const isConstantName = matches(/^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$/u);
|
|
948
|
+
function literal(node) {
|
|
949
|
+
if (node.type === "Literal") return node.value !== null;
|
|
950
|
+
if (node.type === "TemplateLiteral") return isArrayEmpty(node.expressions);
|
|
951
|
+
if (node.type === "UnaryExpression") return node.operator === "-" && literal(node.argument);
|
|
952
|
+
if (node.type === "BinaryExpression") return literal(node.left) && literal(node.right);
|
|
953
|
+
if (node.type === "ArrayExpression") return every(node.elements, (element) => element !== null && element.type !== "SpreadElement" && literal(element));
|
|
954
|
+
if (node.type === "ObjectExpression") return every(node.properties, (property) => property.type === "Property" && !property.computed && property.kind === "init" && literal(property.value));
|
|
955
|
+
return pipe(typeWrapped(node), exists(literal));
|
|
956
|
+
}
|
|
957
|
+
function literalConstant(statement) {
|
|
958
|
+
if (statement.type !== "VariableDeclaration" || statement.kind !== "const" || statement.declarations.length !== 1) return none();
|
|
959
|
+
const [node] = statement.declarations;
|
|
960
|
+
if (node?.id.type !== "Identifier" || node.init === null || !literal(node.init)) return none();
|
|
961
|
+
return some({
|
|
962
|
+
id: node.id,
|
|
963
|
+
node
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
function renamed(input) {
|
|
967
|
+
if (input.reference.identifier.parent.type === "Property" && input.reference.identifier.parent.shorthand) return input.fixer.replaceText(input.reference.identifier, `${input.reference.identifier.name}: ${input.name}`);
|
|
968
|
+
return input.fixer.replaceText(input.reference.identifier, input.name);
|
|
969
|
+
}
|
|
970
|
+
function primitive(node) {
|
|
971
|
+
if (node.type === "Literal") return !hasProperty(node, "regex");
|
|
972
|
+
return contains([
|
|
973
|
+
"BinaryExpression",
|
|
974
|
+
"TemplateLiteral",
|
|
975
|
+
"UnaryExpression"
|
|
976
|
+
], node.type) || pipe(typeWrapped(node), exists(primitive));
|
|
977
|
+
}
|
|
978
|
+
function check$2(input) {
|
|
979
|
+
if (input.found.id.typeAnnotation === null && input.found.node.init !== null && primitive(input.found.node.init) && filter$1(input.variable.references, (reference) => reference.isRead()).length === 1 && !pipe(input.context.sourceCode.getText(input.found.node), includes("\n"))) {
|
|
980
|
+
input.context.report({
|
|
981
|
+
message: "Inline this value at its only use.",
|
|
982
|
+
node: input.found.node
|
|
983
|
+
});
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
if (!isConstantName(input.found.id.name)) {
|
|
987
|
+
const name = pipe(input.found.id.name, replaceAll(/(?<=[a-z0-9])(?=[A-Z])/gu, "_"), toUpperCase);
|
|
988
|
+
const taken = some$1([input.found.id, ...map$1(input.variable.references, (reference) => reference.identifier)], (node) => isSome(variableFromScope({
|
|
989
|
+
name,
|
|
990
|
+
scope: input.context.sourceCode.getScope(node)
|
|
991
|
+
})));
|
|
992
|
+
input.context.report({
|
|
993
|
+
fix: (fixer) => {
|
|
994
|
+
if (taken) return [];
|
|
995
|
+
return [fixer.replaceTextRange([input.found.id.range[0], input.found.id.range[0] + input.found.id.name.length], name), ...pipe(input.variable.references, filter$1((reference) => reference.identifier !== input.found.id), map$1((reference) => renamed({
|
|
996
|
+
fixer,
|
|
997
|
+
name,
|
|
998
|
+
reference
|
|
999
|
+
})))];
|
|
1000
|
+
},
|
|
1001
|
+
message: `Name this module constant ${name}.`,
|
|
1002
|
+
node: input.found.node
|
|
1003
|
+
});
|
|
1004
|
+
}
|
|
1005
|
+
if (!input.leading) input.context.report({
|
|
1006
|
+
message: "Declare this module constant directly under the imports.",
|
|
1007
|
+
node: input.found.node
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
const moduleConstant = defineRule({
|
|
1011
|
+
create: (context) => ({ Program: (program) => {
|
|
1012
|
+
const leading = takeWhile(program.body, (statement) => statement.type === "ImportDeclaration" || isSome(literalConstant(statement)));
|
|
1013
|
+
for (const statement of program.body) pipe(literalConstant(statement), flatMap((found) => pipe(variableFromScope({
|
|
1014
|
+
name: found.id.name,
|
|
1015
|
+
scope: context.sourceCode.getScope(found.node)
|
|
1016
|
+
}), filter((variable) => !some$1(variable.references, (reference) => reference.identifier.parent.type === "ExportSpecifier")), map((variable) => ({
|
|
1017
|
+
found,
|
|
1018
|
+
variable
|
|
1019
|
+
})))), match({
|
|
1020
|
+
onNone: constVoid,
|
|
1021
|
+
onSome: (match) => check$2({
|
|
1022
|
+
...match,
|
|
1023
|
+
context,
|
|
1024
|
+
leading: contains(leading, statement)
|
|
1025
|
+
})
|
|
1026
|
+
}));
|
|
1027
|
+
} }),
|
|
1028
|
+
meta: {
|
|
1029
|
+
fixable: "code",
|
|
1030
|
+
type: "suggestion"
|
|
1031
|
+
}
|
|
1032
|
+
});
|
|
1033
|
+
//#endregion
|
|
1034
|
+
//#region src/rules/motion-limits.ts
|
|
1035
|
+
function utility(token) {
|
|
1036
|
+
return pipe(token, split(":"), last, getOrElse(() => token), replace(/^!|!$/gu, ""));
|
|
1037
|
+
}
|
|
1038
|
+
function amount(token, pattern) {
|
|
1039
|
+
return pipe(utility(token), match$1(pattern), flatMap((match) => fromNullishOr(match[1])), map((value) => pipe(parse(value), getOrElse(() => 0))));
|
|
1040
|
+
}
|
|
1041
|
+
function violation(token) {
|
|
1042
|
+
if (exists(amount(token, /^duration-(\d+)$/u), (value) => value > 300)) return some("Keep a transition under 300 ms: duration-300 or less.");
|
|
1043
|
+
if (matches(/^zoom-(?:in|out)$/u)(utility(token)) || exists(amount(token, /^zoom-(?:in|out)-(\d+)$/u), (value) => value < 90)) return some("Enter from 0.9 scale or more: zoom-in-90 or zoom-in-95.");
|
|
1044
|
+
if (pipe(token, split(":"), dropRight(1), contains("starting")) && exists(amount(token, /^scale-(\d+)$/u), (value) => value < 90)) return some("Enter from 0.9 scale or more: starting:scale-90 or starting:scale-95.");
|
|
1045
|
+
return none();
|
|
1046
|
+
}
|
|
1047
|
+
function check$1(context, node, text) {
|
|
1048
|
+
pipe(split(text, /\s+/u), map$1(violation), getSomes, head, map((message) => context.report({
|
|
1049
|
+
message,
|
|
1050
|
+
node
|
|
1051
|
+
})));
|
|
1052
|
+
}
|
|
1053
|
+
const motionLimits = defineRule({
|
|
1054
|
+
create: (context) => ({
|
|
1055
|
+
Literal: (node) => {
|
|
1056
|
+
if (isString(node.value)) check$1(context, node, node.value);
|
|
1057
|
+
},
|
|
1058
|
+
TemplateElement: (node) => {
|
|
1059
|
+
check$1(context, node, node.value.raw);
|
|
1060
|
+
}
|
|
1061
|
+
}),
|
|
1062
|
+
meta: { type: "problem" }
|
|
1063
|
+
});
|
|
1064
|
+
//#endregion
|
|
893
1065
|
//#region src/rules/no-array-wrap-ternary.ts
|
|
894
1066
|
function testedValue(node) {
|
|
895
|
-
if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression" || node.callee.object.type !== "Identifier" || node.callee.object.name !== "Array" || !contains(memberName(node.callee), "isArray") || node.arguments.length !== 1) return none();
|
|
1067
|
+
if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression" || node.callee.object.type !== "Identifier" || node.callee.object.name !== "Array" || !contains$1(memberName(node.callee), "isArray") || node.arguments.length !== 1) return none();
|
|
896
1068
|
const argument = node.arguments[0];
|
|
897
1069
|
if (argument?.type !== "Identifier" && argument?.type !== "MemberExpression") return none();
|
|
898
1070
|
return some(argument);
|
|
@@ -937,6 +1109,63 @@ const noArrayWrapTernary = defineRule({
|
|
|
937
1109
|
meta: { type: "problem" }
|
|
938
1110
|
});
|
|
939
1111
|
//#endregion
|
|
1112
|
+
//#region src/rules/no-barrel-module.ts
|
|
1113
|
+
const noBarrelModule = defineRule({
|
|
1114
|
+
create: (context) => ({
|
|
1115
|
+
ExportAllDeclaration: (node) => {
|
|
1116
|
+
context.report({
|
|
1117
|
+
message: "Import from the module that defines each name; never re-export a whole module.",
|
|
1118
|
+
node
|
|
1119
|
+
});
|
|
1120
|
+
},
|
|
1121
|
+
Program: (node) => {
|
|
1122
|
+
const statements = filter$1(node.body, (statement) => statement.type !== "ImportDeclaration");
|
|
1123
|
+
if (isArrayEmpty(statements) || !every(statements, (statement) => statement.type === "ExportNamedDeclaration" && statement.source !== null)) return;
|
|
1124
|
+
context.report({
|
|
1125
|
+
message: "Import from the defining modules instead of a file that only re-exports them.",
|
|
1126
|
+
node
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
}),
|
|
1130
|
+
meta: { type: "problem" }
|
|
1131
|
+
});
|
|
1132
|
+
//#endregion
|
|
1133
|
+
//#region src/rules/no-compiler-opt-out.ts
|
|
1134
|
+
const noCompilerOptOut = defineRule({
|
|
1135
|
+
create: (context) => ({ ExpressionStatement: (node) => {
|
|
1136
|
+
if (node.directive !== "use no memo" && node.directive !== "use no forget") return;
|
|
1137
|
+
context.report({
|
|
1138
|
+
message: "Fix the code React Compiler rejects instead of opting the function out of compilation.",
|
|
1139
|
+
node
|
|
1140
|
+
});
|
|
1141
|
+
} }),
|
|
1142
|
+
meta: { type: "problem" }
|
|
1143
|
+
});
|
|
1144
|
+
//#endregion
|
|
1145
|
+
//#region src/rules/no-component-atom-allocation.ts
|
|
1146
|
+
const componentOrHook = matches(/^(?:[A-Z]|use[A-Z])/u);
|
|
1147
|
+
const readsOrWrites = matches(/^(?:is[A-Z]|get|set$|update$|modify$|refresh$|mount$|batch$|toStream)/u);
|
|
1148
|
+
function renderingFunction(node) {
|
|
1149
|
+
return pipe(fromNullishOr(node.parent), flatMap((parent) => {
|
|
1150
|
+
if ((parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression") && exists(bindingName(parent), componentOrHook)) return some(parent);
|
|
1151
|
+
return renderingFunction(parent);
|
|
1152
|
+
}));
|
|
1153
|
+
}
|
|
1154
|
+
const noComponentAtomAllocation = defineRule({
|
|
1155
|
+
create: (context) => ({ CallExpression: (node) => {
|
|
1156
|
+
if (node.callee.type !== "MemberExpression" || node.callee.object.type !== "Identifier" || node.callee.object.name !== "Atom" || !isImportBinding({
|
|
1157
|
+
context,
|
|
1158
|
+
node: node.callee.object,
|
|
1159
|
+
source: /reactivity|atom/u
|
|
1160
|
+
}) || exists(memberName(node.callee), readsOrWrites) || isNone(renderingFunction(node))) return;
|
|
1161
|
+
context.report({
|
|
1162
|
+
message: "Declare the atom at module level, or in Atom.family keyed by the prop; an atom made during render is a new atom on every render.",
|
|
1163
|
+
node
|
|
1164
|
+
});
|
|
1165
|
+
} }),
|
|
1166
|
+
meta: { type: "problem" }
|
|
1167
|
+
});
|
|
1168
|
+
//#endregion
|
|
940
1169
|
//#region src/rules/no-constant-function.ts
|
|
941
1170
|
function isConstantFunction(input) {
|
|
942
1171
|
if (input.node.async || input.node.generator || isArrayNonEmpty(input.node.params) || isNotNullish(input.node.typeParameters)) return false;
|
|
@@ -986,7 +1215,7 @@ function isPipeCall(context, node) {
|
|
|
986
1215
|
});
|
|
987
1216
|
}
|
|
988
1217
|
function enclosingPipes(context, node) {
|
|
989
|
-
if (node.type === "Program" || contains
|
|
1218
|
+
if (node.type === "Program" || contains([
|
|
990
1219
|
"ArrowFunctionExpression",
|
|
991
1220
|
"FunctionDeclaration",
|
|
992
1221
|
"FunctionExpression"
|
|
@@ -1007,7 +1236,7 @@ const noDeepPipe = defineRule({
|
|
|
1007
1236
|
//#endregion
|
|
1008
1237
|
//#region src/rules/no-destructured-parameter.ts
|
|
1009
1238
|
function namedTuple(input) {
|
|
1010
|
-
if (contains
|
|
1239
|
+
if (contains(input.seen, input.node)) return false;
|
|
1011
1240
|
if (input.node.type === "TSTupleType") return isArrayNonEmpty(input.node.elementTypes) && every(input.node.elementTypes, (element) => element.type === "TSNamedTupleMember");
|
|
1012
1241
|
if (input.node.type === "TSParenthesizedType" || input.node.type === "TSTypeOperator" && input.node.operator === "readonly") return namedTuple({
|
|
1013
1242
|
...input,
|
|
@@ -1081,6 +1310,9 @@ const noDestructuredParameter = defineRule({
|
|
|
1081
1310
|
});
|
|
1082
1311
|
//#endregion
|
|
1083
1312
|
//#region src/rules/no-discarded-failure.ts
|
|
1313
|
+
function logged(node) {
|
|
1314
|
+
return node.parent.type === "CallExpression" && node.parent.callee === node && some$1(node.parent.arguments, (argument) => argument.type === "ObjectExpression" && every(argument.properties, (property) => property.type === "Property") && some$1(argument.properties, (property) => !property.computed && (property.key.type === "Identifier" && property.key.name === "log" || property.key.type === "Literal" && property.key.value === "log") && property.value.type === "Literal" && (property.value.value === true || isString(property.value.value))));
|
|
1315
|
+
}
|
|
1084
1316
|
const noDiscardedFailure = defineRule({
|
|
1085
1317
|
create: (context) => ({ MemberExpression: (node) => {
|
|
1086
1318
|
if (!importedMember({
|
|
@@ -1088,13 +1320,14 @@ const noDiscardedFailure = defineRule({
|
|
|
1088
1320
|
importedName: "Effect",
|
|
1089
1321
|
node
|
|
1090
1322
|
})) return;
|
|
1091
|
-
if (!exists(memberName(node), (name) => contains
|
|
1323
|
+
if (!exists(memberName(node), (name) => contains([
|
|
1092
1324
|
"ignore",
|
|
1093
1325
|
"orDie",
|
|
1094
1326
|
"orDieWith"
|
|
1095
1327
|
], name))) return;
|
|
1328
|
+
if (contains$1(memberName(node), "ignore") && logged(node)) return;
|
|
1096
1329
|
context.report({
|
|
1097
|
-
message: "Map the failure to the service's error; an expected failure is never
|
|
1330
|
+
message: "Map the failure to the service's error, or log best-effort work with Effect.ignore({log: 'Warn', message}); an expected failure is never dropped silently or turned into a defect.",
|
|
1098
1331
|
node
|
|
1099
1332
|
});
|
|
1100
1333
|
} }),
|
|
@@ -1102,12 +1335,7 @@ const noDiscardedFailure = defineRule({
|
|
|
1102
1335
|
});
|
|
1103
1336
|
//#endregion
|
|
1104
1337
|
//#region src/rules/no-double-nullish-check.ts
|
|
1105
|
-
|
|
1106
|
-
if (node.type === "Literal" && node.value === null) return some("null");
|
|
1107
|
-
if (node.type === "Identifier" && node.name === "undefined") return some("undefined");
|
|
1108
|
-
return none();
|
|
1109
|
-
}
|
|
1110
|
-
const variants = {
|
|
1338
|
+
const VARIANTS = {
|
|
1111
1339
|
"&&": {
|
|
1112
1340
|
comparison: "!==",
|
|
1113
1341
|
message: "Use Predicate.isNotNullish."
|
|
@@ -1117,6 +1345,11 @@ const variants = {
|
|
|
1117
1345
|
message: "Use Predicate.isNullish."
|
|
1118
1346
|
}
|
|
1119
1347
|
};
|
|
1348
|
+
function nullishOperand(node) {
|
|
1349
|
+
if (node.type === "Literal" && node.value === null) return some("null");
|
|
1350
|
+
if (node.type === "Identifier" && node.name === "undefined") return some("undefined");
|
|
1351
|
+
return none();
|
|
1352
|
+
}
|
|
1120
1353
|
function operands(input) {
|
|
1121
1354
|
if (input.node.type !== "LogicalExpression" || input.node.operator !== input.operator) return [input.node];
|
|
1122
1355
|
return appendAll(operands({
|
|
@@ -1139,7 +1372,7 @@ const noDoubleNullishCheck = defineRule({
|
|
|
1139
1372
|
create: (context) => ({ LogicalExpression: (node) => {
|
|
1140
1373
|
if (node.operator === "??") return;
|
|
1141
1374
|
if (node.parent.type === "LogicalExpression" && node.parent.operator === node.operator) return;
|
|
1142
|
-
const variant =
|
|
1375
|
+
const variant = VARIANTS[node.operator];
|
|
1143
1376
|
const checks = pipe(operands({
|
|
1144
1377
|
node,
|
|
1145
1378
|
operator: node.operator
|
|
@@ -1186,6 +1419,80 @@ const noEffectPropertyArrow = defineRule({
|
|
|
1186
1419
|
meta: { type: "problem" }
|
|
1187
1420
|
});
|
|
1188
1421
|
//#endregion
|
|
1422
|
+
//#region src/rules/no-empty-recovery.ts
|
|
1423
|
+
function calleeOf(node) {
|
|
1424
|
+
if (node.type === "CallExpression" && node.callee.type !== "Super") return node.callee;
|
|
1425
|
+
return node;
|
|
1426
|
+
}
|
|
1427
|
+
function isEffectEmpty(input) {
|
|
1428
|
+
const member = calleeOf(input.node);
|
|
1429
|
+
return member.type === "MemberExpression" && member.object.type === "Identifier" && contains$1(memberName(member), "empty") && isImportBinding({
|
|
1430
|
+
context: input.context,
|
|
1431
|
+
node: member.object,
|
|
1432
|
+
source: "effect"
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
function isEmptyValue(input) {
|
|
1436
|
+
if (isNullish$1(input.node)) return false;
|
|
1437
|
+
if (input.node.type === "Identifier") return input.node.name === "undefined";
|
|
1438
|
+
if (input.node.type === "Literal") return input.node.value === null || input.node.value === "";
|
|
1439
|
+
if (input.node.type === "ArrayExpression") return isArrayEmpty(input.node.elements);
|
|
1440
|
+
if (input.node.type === "ObjectExpression") return isArrayEmpty(input.node.properties);
|
|
1441
|
+
if (input.node.type === "TemplateLiteral") return isArrayEmpty(input.node.expressions) && every(input.node.quasis, (quasi) => quasi.value.raw === "");
|
|
1442
|
+
return isEffectEmpty({
|
|
1443
|
+
context: input.context,
|
|
1444
|
+
node: input.node
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
function isEmptySuccess(input) {
|
|
1448
|
+
if (isNullish$1(input.node)) return false;
|
|
1449
|
+
if (importedMember({
|
|
1450
|
+
context: input.context,
|
|
1451
|
+
importedName: "Effect",
|
|
1452
|
+
node: input.node,
|
|
1453
|
+
propertyName: "void"
|
|
1454
|
+
})) return true;
|
|
1455
|
+
return input.node.type === "CallExpression" && input.node.callee.type !== "Super" && importedMember({
|
|
1456
|
+
context: input.context,
|
|
1457
|
+
importedName: "Effect",
|
|
1458
|
+
node: input.node.callee,
|
|
1459
|
+
propertyName: "succeed"
|
|
1460
|
+
}) && every(input.node.arguments, (argument) => argument.type !== "SpreadElement" && isEmptyValue({
|
|
1461
|
+
context: input.context,
|
|
1462
|
+
node: argument
|
|
1463
|
+
}));
|
|
1464
|
+
}
|
|
1465
|
+
function recoversEmpty(input) {
|
|
1466
|
+
if (input.name === "orElseSucceed") return isEmptyValue({
|
|
1467
|
+
context: input.context,
|
|
1468
|
+
node: input.node
|
|
1469
|
+
});
|
|
1470
|
+
return isEmptySuccess({
|
|
1471
|
+
context: input.context,
|
|
1472
|
+
node: input.node
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
const noEmptyRecovery = defineRule({
|
|
1476
|
+
create: (context) => ({ CallExpression: (node) => {
|
|
1477
|
+
if (node.callee.type !== "MemberExpression" || !importedMember({
|
|
1478
|
+
context,
|
|
1479
|
+
importedName: "Effect",
|
|
1480
|
+
node: node.callee
|
|
1481
|
+
})) return;
|
|
1482
|
+
const name = pipe(memberName(node.callee), getOrElse(() => ""));
|
|
1483
|
+
if (!pipe(name, startsWith("catch")) && !pipe(name, startsWith("orElse"))) return;
|
|
1484
|
+
for (const argument of node.arguments) if ((argument.type === "ArrowFunctionExpression" || argument.type === "FunctionExpression") && recoversEmpty({
|
|
1485
|
+
context,
|
|
1486
|
+
name,
|
|
1487
|
+
node: returnedExpression(argument)
|
|
1488
|
+
})) context.report({
|
|
1489
|
+
message: "Let the failure flow, or map it to the service’s error; an empty value hides that the work failed.",
|
|
1490
|
+
node: argument
|
|
1491
|
+
});
|
|
1492
|
+
} }),
|
|
1493
|
+
meta: { type: "problem" }
|
|
1494
|
+
});
|
|
1495
|
+
//#endregion
|
|
1189
1496
|
//#region src/rules/no-error-message-assertion.ts
|
|
1190
1497
|
function receiverName(node) {
|
|
1191
1498
|
if (node.type === "Identifier") return some(node.name);
|
|
@@ -1193,13 +1500,13 @@ function receiverName(node) {
|
|
|
1193
1500
|
return memberName(node);
|
|
1194
1501
|
}
|
|
1195
1502
|
function isErrorMessage(node) {
|
|
1196
|
-
return node?.type === "MemberExpression" && contains(memberName(node), "message") && exists(receiverName(node.object), matches(/^(?:e|err|error|failure|cause|exception)$|Error$/iu));
|
|
1503
|
+
return node?.type === "MemberExpression" && contains$1(memberName(node), "message") && exists(receiverName(node.object), matches(/^(?:e|err|error|failure|cause|exception)$|Error$/iu));
|
|
1197
1504
|
}
|
|
1198
1505
|
function expectsErrorMessage(node) {
|
|
1199
1506
|
return node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "expect" && isErrorMessage(node.arguments[0]);
|
|
1200
1507
|
}
|
|
1201
1508
|
function expectsMessage(node) {
|
|
1202
|
-
if (node.object.type === "MemberExpression" && contains(memberName(node.object), "not")) return expectsErrorMessage(node.object.object);
|
|
1509
|
+
if (node.object.type === "MemberExpression" && contains$1(memberName(node.object), "not")) return expectsErrorMessage(node.object.object);
|
|
1203
1510
|
return expectsErrorMessage(node.object);
|
|
1204
1511
|
}
|
|
1205
1512
|
function assertsMessage(node) {
|
|
@@ -1215,7 +1522,7 @@ const noErrorMessageAssertion = defineRule({
|
|
|
1215
1522
|
});
|
|
1216
1523
|
return;
|
|
1217
1524
|
}
|
|
1218
|
-
if (node.callee.type === "MemberExpression" && (node.arguments[0]?.type === "Literal" || node.arguments[0]?.type === "TemplateLiteral") && (exists(memberName(node.callee), (name) => contains
|
|
1525
|
+
if (node.callee.type === "MemberExpression" && (node.arguments[0]?.type === "Literal" || node.arguments[0]?.type === "TemplateLiteral") && (exists(memberName(node.callee), (name) => contains(["toThrow", "toThrowError"], name)) || expectsMessage(node.callee))) context.report({
|
|
1219
1526
|
message: "Assert the failure, never its message wording.",
|
|
1220
1527
|
node
|
|
1221
1528
|
});
|
|
@@ -1241,7 +1548,7 @@ function hasUnusedSetter(input) {
|
|
|
1241
1548
|
}
|
|
1242
1549
|
function isFakeRefState(input) {
|
|
1243
1550
|
if (!isReactUseState(input) || input.node.arguments[0]?.type !== "ArrowFunctionExpression" || input.node.arguments[0].body.type !== "ObjectExpression" || input.node.arguments[0].body.properties.length !== 1 || !hasUnusedSetter(input)) return false;
|
|
1244
|
-
return some$1(input.node.arguments[0].body.properties, (property) => property.type === "Property" && contains(propertyName(property), "current") && property.value.type === "Literal" && property.value.value === null);
|
|
1551
|
+
return some$1(input.node.arguments[0].body.properties, (property) => property.type === "Property" && contains$1(propertyName(property), "current") && property.value.type === "Literal" && property.value.value === null);
|
|
1245
1552
|
}
|
|
1246
1553
|
const noFakeRefState = defineRule({
|
|
1247
1554
|
create: (context) => ({ CallExpression: (node) => {
|
|
@@ -1256,6 +1563,49 @@ const noFakeRefState = defineRule({
|
|
|
1256
1563
|
meta: { type: "problem" }
|
|
1257
1564
|
});
|
|
1258
1565
|
//#endregion
|
|
1566
|
+
//#region src/rules/no-floating-derived-type.ts
|
|
1567
|
+
function derived(node) {
|
|
1568
|
+
if (node.type === "TSIndexedAccessType") return derived(node.objectType);
|
|
1569
|
+
return node.type === "TSTypeReference" && node.typeName.type === "Identifier" && contains([
|
|
1570
|
+
"Awaited",
|
|
1571
|
+
"ConstructorParameters",
|
|
1572
|
+
"InstanceType",
|
|
1573
|
+
"Parameters",
|
|
1574
|
+
"ReturnType"
|
|
1575
|
+
], node.typeName.name);
|
|
1576
|
+
}
|
|
1577
|
+
const noFloatingDerivedType = defineRule({
|
|
1578
|
+
create: (context) => ({ TSTypeAliasDeclaration: (node) => {
|
|
1579
|
+
const listed = some$1(context.sourceCode.ast.body, (statement) => statement.type === "ExportNamedDeclaration" && some$1(statement.specifiers, (specifier) => specifier.local.type === "Identifier" && specifier.local.name === node.id.name));
|
|
1580
|
+
if (node.parent.type === "ExportNamedDeclaration" || listed || !derived(node.typeAnnotation)) return;
|
|
1581
|
+
context.report({
|
|
1582
|
+
message: "Import the library's own type, or pass the value straight to the call that types it.",
|
|
1583
|
+
node
|
|
1584
|
+
});
|
|
1585
|
+
} }),
|
|
1586
|
+
meta: { type: "problem" }
|
|
1587
|
+
});
|
|
1588
|
+
//#endregion
|
|
1589
|
+
//#region src/rules/no-gradient.ts
|
|
1590
|
+
const GRADIENT = /(?:^|[\s:])bg-(?:linear|radial|conic|gradient)(?:-|$|\s)|gradient\(/u;
|
|
1591
|
+
function report$4(context, node) {
|
|
1592
|
+
context.report({
|
|
1593
|
+
message: "Use a flat theme color; gradients are never part of the design.",
|
|
1594
|
+
node
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
const noGradient = defineRule({
|
|
1598
|
+
create: (context) => ({
|
|
1599
|
+
Literal: (node) => {
|
|
1600
|
+
if (isString(node.value) && GRADIENT.test(node.value)) report$4(context, node);
|
|
1601
|
+
},
|
|
1602
|
+
TemplateElement: (node) => {
|
|
1603
|
+
if (GRADIENT.test(node.value.raw)) report$4(context, node);
|
|
1604
|
+
}
|
|
1605
|
+
}),
|
|
1606
|
+
meta: { type: "problem" }
|
|
1607
|
+
});
|
|
1608
|
+
//#endregion
|
|
1259
1609
|
//#region src/rules/no-hand-written-guard.ts
|
|
1260
1610
|
function isSubject(input) {
|
|
1261
1611
|
return input.node.type === "Identifier" && input.node.name === input.name;
|
|
@@ -1284,7 +1634,7 @@ function check(input) {
|
|
|
1284
1634
|
if (input.node.type === "UnaryExpression" && input.node.operator === "!" && input.node.argument.type === "CallExpression" && input.node.argument.callee.type === "MemberExpression" && isSubject({
|
|
1285
1635
|
name: "Array",
|
|
1286
1636
|
node: input.node.argument.callee.object
|
|
1287
|
-
}) && contains(memberName(input.node.argument.callee), "isArray") && input.node.argument.arguments.length === 1 && every(input.node.argument.arguments, (argument) => isSubject({
|
|
1637
|
+
}) && contains$1(memberName(input.node.argument.callee), "isArray") && input.node.argument.arguments.length === 1 && every(input.node.argument.arguments, (argument) => isSubject({
|
|
1288
1638
|
name: input.name,
|
|
1289
1639
|
node: argument
|
|
1290
1640
|
}))) return some(["!Array.isArray"]);
|
|
@@ -1308,11 +1658,11 @@ function check(input) {
|
|
|
1308
1658
|
subject: input.node.right
|
|
1309
1659
|
})]), map(of));
|
|
1310
1660
|
}
|
|
1311
|
-
function report$
|
|
1661
|
+
function report$3(input) {
|
|
1312
1662
|
const predicate = input.node.returnType?.typeAnnotation;
|
|
1313
1663
|
const returned = returnedExpression(input.node);
|
|
1314
1664
|
if (predicate?.type !== "TSTypePredicate" || predicate.asserts || predicate.parameterName.type !== "Identifier" || isNullish$1(returned)) return;
|
|
1315
|
-
|
|
1665
|
+
map(pipe(check({
|
|
1316
1666
|
name: predicate.parameterName.name,
|
|
1317
1667
|
node: returned
|
|
1318
1668
|
}), map((checks) => pipe(checks, sort(Order), join(" && "))), flatMap((key) => get$2({
|
|
@@ -1337,8 +1687,7 @@ function report$2(input) {
|
|
|
1337
1687
|
"typeof === string": "isString",
|
|
1338
1688
|
"typeof === symbol": "isSymbol",
|
|
1339
1689
|
"typeof === undefined": "isUndefined"
|
|
1340
|
-
}, key)))
|
|
1341
|
-
map(name, (guard) => {
|
|
1690
|
+
}, key))), (guard) => {
|
|
1342
1691
|
input.context.report({
|
|
1343
1692
|
message: `Use Predicate.${guard} instead of this hand-written type guard.`,
|
|
1344
1693
|
node: input.node
|
|
@@ -1348,19 +1697,19 @@ function report$2(input) {
|
|
|
1348
1697
|
const noHandWrittenGuard = defineRule({
|
|
1349
1698
|
create: (context) => ({
|
|
1350
1699
|
ArrowFunctionExpression: (node) => {
|
|
1351
|
-
report$
|
|
1700
|
+
report$3({
|
|
1352
1701
|
context,
|
|
1353
1702
|
node
|
|
1354
1703
|
});
|
|
1355
1704
|
},
|
|
1356
1705
|
FunctionDeclaration: (node) => {
|
|
1357
|
-
report$
|
|
1706
|
+
report$3({
|
|
1358
1707
|
context,
|
|
1359
1708
|
node
|
|
1360
1709
|
});
|
|
1361
1710
|
},
|
|
1362
1711
|
FunctionExpression: (node) => {
|
|
1363
|
-
report$
|
|
1712
|
+
report$3({
|
|
1364
1713
|
context,
|
|
1365
1714
|
node
|
|
1366
1715
|
});
|
|
@@ -1393,13 +1742,12 @@ const noLet = defineRule({
|
|
|
1393
1742
|
});
|
|
1394
1743
|
//#endregion
|
|
1395
1744
|
//#region src/rules/no-multiline-comment.ts
|
|
1396
|
-
const directive = /^\s*(?:eslint|oxlint|@effect-diagnostics|@ts-|\/\s*<reference|#region|#endregion)/u;
|
|
1397
1745
|
function adjacentLines(pair) {
|
|
1398
1746
|
return pair.first?.type === "Line" && pair.second?.type === "Line" && pair.second.loc.start.line === pair.first.loc.end.line + 1;
|
|
1399
1747
|
}
|
|
1400
1748
|
const noMultilineComment = defineRule({
|
|
1401
1749
|
create: (context) => ({ Program: () => {
|
|
1402
|
-
const comments = pipe(context.sourceCode.getAllComments(), filter$1((comment) => !
|
|
1750
|
+
const comments = pipe(context.sourceCode.getAllComments(), filter$1((comment) => !matches(/^\s*(?:eslint|oxlint|@effect-diagnostics|@ts-|\/\s*<reference|#region|#endregion)/u)(comment.value)));
|
|
1403
1751
|
pipe(comments, filter$1((comment, index) => adjacentLines({
|
|
1404
1752
|
first: comment,
|
|
1405
1753
|
second: comments[index + 1]
|
|
@@ -1415,22 +1763,32 @@ const noMultilineComment = defineRule({
|
|
|
1415
1763
|
});
|
|
1416
1764
|
//#endregion
|
|
1417
1765
|
//#region src/rules/no-native-control.ts
|
|
1418
|
-
const
|
|
1766
|
+
const PRIMITIVES = {
|
|
1419
1767
|
button: "Button",
|
|
1420
1768
|
input: "Input",
|
|
1421
1769
|
select: "Select",
|
|
1422
1770
|
textarea: "Textarea"
|
|
1423
1771
|
};
|
|
1424
|
-
const
|
|
1772
|
+
const CONTROL_STYLE = /(?:^|:)(?:hover|focus|focus-visible|active):|(?:^|:)(?:rounded|shadow|ring)(?:-|$)|(?:^|:)p[blrtxy]?-(?!0$)|(?:^|:)bg-(?!transparent$)|(?:^|:)border(?:-[blrtxy])?$|(?:^|:)border-(?!0$|[blrtxy]-0$)/u;
|
|
1773
|
+
function classStrings(node) {
|
|
1774
|
+
if (node.type === "Literal") return filter$1([node.value], isString);
|
|
1775
|
+
if (node.type === "TemplateLiteral") return map$1(node.quasis, (quasi) => quasi.value.raw);
|
|
1776
|
+
if (node.type === "JSXExpressionContainer") return classStrings(node.expression);
|
|
1777
|
+
if (node.type === "CallExpression") return flatMap$1(node.arguments, classStrings);
|
|
1778
|
+
if (node.type === "ConditionalExpression") return flatMap$1([node.consequent, node.alternate], classStrings);
|
|
1779
|
+
if (node.type === "LogicalExpression") return classStrings(node.right);
|
|
1780
|
+
if (node.type === "ArrayExpression") return flatMap$1(filter$1(node.elements, isNotNull), classStrings);
|
|
1781
|
+
return empty();
|
|
1782
|
+
}
|
|
1425
1783
|
const noNativeControl = defineRule({
|
|
1426
1784
|
create: (context) => ({ JSXOpeningElement: (node) => {
|
|
1427
1785
|
if (node.name.type !== "JSXIdentifier") return;
|
|
1428
1786
|
const name = node.name.name;
|
|
1429
|
-
map(get$2(
|
|
1430
|
-
const styled = pipe(node.attributes, findFirst((attribute) => attribute.type === "JSXAttribute" && attribute.name.type === "JSXIdentifier" && attribute.name.name === "className"),
|
|
1431
|
-
if (attribute.type !== "JSXAttribute" || attribute.value
|
|
1432
|
-
return
|
|
1433
|
-
}), exists((value) => some$1(split(value, /\s+/u), matches(
|
|
1787
|
+
map(get$2(PRIMITIVES, name), (primitive) => {
|
|
1788
|
+
const styled = pipe(node.attributes, findFirst((attribute) => attribute.type === "JSXAttribute" && attribute.name.type === "JSXIdentifier" && attribute.name.name === "className"), map((attribute) => {
|
|
1789
|
+
if (attribute.type !== "JSXAttribute" || attribute.value === null) return empty();
|
|
1790
|
+
return classStrings(attribute.value);
|
|
1791
|
+
}), exists((values) => some$1(values, (value) => some$1(split(value, /\s+/u), matches(CONTROL_STYLE)))));
|
|
1434
1792
|
if (name !== "select" && !styled) return;
|
|
1435
1793
|
context.report({
|
|
1436
1794
|
message: `Use the ${primitive} UI primitive instead of a styled native control.`,
|
|
@@ -1443,7 +1801,7 @@ const noNativeControl = defineRule({
|
|
|
1443
1801
|
//#endregion
|
|
1444
1802
|
//#region src/rules/no-native-emptiness-check.ts
|
|
1445
1803
|
function emptinessPredicate(node) {
|
|
1446
|
-
const comparesLength = node.left.type === "MemberExpression" && contains(memberName(node.left), "length") && node.right.type === "Literal" && node.right.value === 0;
|
|
1804
|
+
const comparesLength = node.left.type === "MemberExpression" && contains$1(memberName(node.left), "length") && node.right.type === "Literal" && node.right.value === 0;
|
|
1447
1805
|
if (comparesLength && node.operator === "===") return some("Array.isArrayEmpty, Array.isReadonlyArrayEmpty, or String.isEmpty");
|
|
1448
1806
|
if (comparesLength && node.operator === ">") return some("Array.isArrayNonEmpty, Array.isReadonlyArrayNonEmpty, or String.isNonEmpty");
|
|
1449
1807
|
return none();
|
|
@@ -1461,7 +1819,7 @@ const noNativeEmptinessCheck = defineRule({
|
|
|
1461
1819
|
});
|
|
1462
1820
|
//#endregion
|
|
1463
1821
|
//#region src/rules/no-native-method-call.ts
|
|
1464
|
-
const
|
|
1822
|
+
const NATIVE_METHODS = [
|
|
1465
1823
|
"at",
|
|
1466
1824
|
"catch",
|
|
1467
1825
|
"charAt",
|
|
@@ -1532,13 +1890,48 @@ function freshRegExpFlags(input) {
|
|
|
1532
1890
|
if (flags.type !== "Literal" || !isString(flags.value)) return some(none());
|
|
1533
1891
|
return some(some(flags.value));
|
|
1534
1892
|
}
|
|
1893
|
+
function memberRoot(node) {
|
|
1894
|
+
if (node.type === "MemberExpression") return memberRoot(node.object);
|
|
1895
|
+
return node;
|
|
1896
|
+
}
|
|
1897
|
+
function chainRoot(node) {
|
|
1898
|
+
if (node.type === "MemberExpression") return chainRoot(node.object);
|
|
1899
|
+
if (node.type === "CallExpression" && node.callee.type !== "Super") return chainRoot(node.callee);
|
|
1900
|
+
if (node.type === "ParenthesizedExpression") return chainRoot(node.expression);
|
|
1901
|
+
return node;
|
|
1902
|
+
}
|
|
1903
|
+
function isYieldedService(node) {
|
|
1904
|
+
if (node?.type !== "YieldExpression" || !node.delegate || isNullish$1(node.argument)) return false;
|
|
1905
|
+
if (node.argument.type === "Identifier") return matches(/^[A-Z]/u)(node.argument.name);
|
|
1906
|
+
return node.argument.type === "MemberExpression" && exists(memberName(node.argument), (name) => matches(/^[A-Z]/u)(name));
|
|
1907
|
+
}
|
|
1908
|
+
const effectPackage = matches(/^(?:effect(?:\/|$)|@effect\/)/u);
|
|
1909
|
+
const localPath = matches(/^[.#~/]|^@\//u);
|
|
1910
|
+
function ownsMethod(input) {
|
|
1911
|
+
const root = chainRoot(input.receiver);
|
|
1912
|
+
const throughCall = root !== memberRoot(input.receiver);
|
|
1913
|
+
if (isYieldedService(root)) return true;
|
|
1914
|
+
if (root.type !== "Identifier") return false;
|
|
1915
|
+
if (root.name === "path") return !throughCall;
|
|
1916
|
+
return pipe(variableFor(input.context, root), filter((variable) => isReadonlyArrayNonEmpty(variable.defs)), match({
|
|
1917
|
+
onNone: () => !throughCall && matches(/^[A-Z]/u)(root.name) && !contains([
|
|
1918
|
+
"Object",
|
|
1919
|
+
"Promise",
|
|
1920
|
+
"RegExp"
|
|
1921
|
+
], root.name),
|
|
1922
|
+
onSome: (variable) => some$1(variable.defs, (definition) => {
|
|
1923
|
+
if (definition.node.type === "VariableDeclarator") return !throughCall && isYieldedService(definition.node.init);
|
|
1924
|
+
if (definition.type !== "ImportBinding" || definition.parent?.type !== "ImportDeclaration") return false;
|
|
1925
|
+
return !localPath(definition.parent.source.value) && !(throughCall && effectPackage(definition.parent.source.value));
|
|
1926
|
+
})
|
|
1927
|
+
}));
|
|
1928
|
+
}
|
|
1535
1929
|
const noNativeMethodCall = defineRule({
|
|
1536
1930
|
create: (context) => ({ CallExpression: (node) => {
|
|
1537
|
-
if (node.callee.type !== "MemberExpression" ||
|
|
1931
|
+
if (node.callee.type !== "MemberExpression" || ownsMethod({
|
|
1538
1932
|
context,
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
}))) return;
|
|
1933
|
+
receiver: node.callee.object
|
|
1934
|
+
})) return;
|
|
1542
1935
|
const receiver = node.callee.object;
|
|
1543
1936
|
const regExpFlags = freshRegExpFlags({
|
|
1544
1937
|
context,
|
|
@@ -1551,29 +1944,19 @@ const noNativeMethodCall = defineRule({
|
|
|
1551
1944
|
});
|
|
1552
1945
|
return;
|
|
1553
1946
|
}
|
|
1554
|
-
pipe(memberName(node.callee), filter((name) => contains
|
|
1947
|
+
pipe(memberName(node.callee), filter((name) => contains(NATIVE_METHODS, name)), filter((name) => !contains([
|
|
1555
1948
|
"entries",
|
|
1556
1949
|
"keys",
|
|
1557
1950
|
"values"
|
|
1558
|
-
], name) || isArrayEmpty(node.arguments) || receiver.type === "Identifier" && receiver.name === "Object"), map((name) => {
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
});
|
|
1563
|
-
}));
|
|
1951
|
+
], name) || isArrayEmpty(node.arguments) || receiver.type === "Identifier" && receiver.name === "Object"), map((name) => context.report({
|
|
1952
|
+
message: `Replace .${name}() on a native array, string, record, or Promise with its Effect Array, String, Record, or Effect function; a third-party API's own method stays, with an inline disable and its reason.`,
|
|
1953
|
+
node: node.callee
|
|
1954
|
+
})));
|
|
1564
1955
|
} }),
|
|
1565
1956
|
meta: { type: "problem" }
|
|
1566
1957
|
});
|
|
1567
1958
|
//#endregion
|
|
1568
1959
|
//#region src/rules/no-nullish-ternary.ts
|
|
1569
|
-
const nullishPredicates = [
|
|
1570
|
-
"isNotNull",
|
|
1571
|
-
"isNotNullish",
|
|
1572
|
-
"isNotUndefined",
|
|
1573
|
-
"isNull",
|
|
1574
|
-
"isNullish",
|
|
1575
|
-
"isUndefined"
|
|
1576
|
-
];
|
|
1577
1960
|
function isNullish(input) {
|
|
1578
1961
|
if (input.node.type === "Literal") return input.node.value === null;
|
|
1579
1962
|
return input.node.type === "Identifier" && input.node.name === "undefined";
|
|
@@ -1594,7 +1977,14 @@ function testsNullish(input) {
|
|
|
1594
1977
|
context: input.context,
|
|
1595
1978
|
importedName: "Predicate",
|
|
1596
1979
|
node: input.node.callee
|
|
1597
|
-
}) && exists(memberName(input.node.callee), (name) => contains
|
|
1980
|
+
}) && exists(memberName(input.node.callee), (name) => contains([
|
|
1981
|
+
"isNotNull",
|
|
1982
|
+
"isNotNullish",
|
|
1983
|
+
"isNotUndefined",
|
|
1984
|
+
"isNull",
|
|
1985
|
+
"isNullish",
|
|
1986
|
+
"isUndefined"
|
|
1987
|
+
], name));
|
|
1598
1988
|
}
|
|
1599
1989
|
const noNullishTernary = defineRule({
|
|
1600
1990
|
create: (context) => ({ ConditionalExpression: (node) => {
|
|
@@ -1626,7 +2016,7 @@ function checks(input) {
|
|
|
1626
2016
|
}) && input.test.arguments[0] !== void 0 && input.context.sourceCode.getText(input.test.arguments[0]) === input.subject;
|
|
1627
2017
|
}
|
|
1628
2018
|
function exits(node) {
|
|
1629
|
-
return contains
|
|
2019
|
+
return contains([
|
|
1630
2020
|
"ContinueStatement",
|
|
1631
2021
|
"ReturnStatement",
|
|
1632
2022
|
"ThrowStatement"
|
|
@@ -1663,7 +2053,7 @@ function guarded(input) {
|
|
|
1663
2053
|
}
|
|
1664
2054
|
const noOptionValueAccess = defineRule({
|
|
1665
2055
|
create: (context) => ({ MemberExpression: (node) => {
|
|
1666
|
-
if (!contains(memberName(node), "value")) return;
|
|
2056
|
+
if (!contains$1(memberName(node), "value")) return;
|
|
1667
2057
|
if (guarded({
|
|
1668
2058
|
context,
|
|
1669
2059
|
node,
|
|
@@ -1677,26 +2067,14 @@ const noOptionValueAccess = defineRule({
|
|
|
1677
2067
|
});
|
|
1678
2068
|
//#endregion
|
|
1679
2069
|
//#region src/rules/no-property-alias.ts
|
|
1680
|
-
const
|
|
2070
|
+
const FUNCTIONS = [
|
|
1681
2071
|
"ArrowFunctionExpression",
|
|
1682
2072
|
"FunctionDeclaration",
|
|
1683
2073
|
"FunctionExpression"
|
|
1684
2074
|
];
|
|
1685
|
-
const deferred = [
|
|
1686
|
-
"ChainExpression",
|
|
1687
|
-
"ConditionalExpression",
|
|
1688
|
-
"DoWhileStatement",
|
|
1689
|
-
"ForInStatement",
|
|
1690
|
-
"ForOfStatement",
|
|
1691
|
-
"ForStatement",
|
|
1692
|
-
"IfStatement",
|
|
1693
|
-
"LogicalExpression",
|
|
1694
|
-
"SwitchStatement",
|
|
1695
|
-
"WhileStatement"
|
|
1696
|
-
];
|
|
1697
2075
|
function insideFunction(node) {
|
|
1698
2076
|
if (isNullish$1(node.parent) || node.parent.type === "Program") return false;
|
|
1699
|
-
if (contains
|
|
2077
|
+
if (contains(FUNCTIONS, node.parent.type)) return true;
|
|
1700
2078
|
return insideFunction(node.parent);
|
|
1701
2079
|
}
|
|
1702
2080
|
function sameRoot(input) {
|
|
@@ -1708,9 +2086,58 @@ function sameRoot(input) {
|
|
|
1708
2086
|
scope: input.context.sourceCode.getScope(input.read)
|
|
1709
2087
|
}));
|
|
1710
2088
|
}
|
|
2089
|
+
function pathTop(node) {
|
|
2090
|
+
if (node.parent?.type === "MemberExpression" && node.parent.object === node && !node.parent.computed) return pathTop(node.parent);
|
|
2091
|
+
return node;
|
|
2092
|
+
}
|
|
2093
|
+
function tested(node) {
|
|
2094
|
+
if (isNullish$1(node.parent)) return false;
|
|
2095
|
+
if (node.parent.type === "LogicalExpression") return node.parent.left === node || tested(node.parent);
|
|
2096
|
+
if (contains([
|
|
2097
|
+
"ChainExpression",
|
|
2098
|
+
"TSAsExpression",
|
|
2099
|
+
"TSNonNullExpression"
|
|
2100
|
+
], node.parent.type)) return tested(node.parent);
|
|
2101
|
+
return node.parent.type === "BinaryExpression" && contains([
|
|
2102
|
+
"!=",
|
|
2103
|
+
"!==",
|
|
2104
|
+
"==",
|
|
2105
|
+
"===",
|
|
2106
|
+
"in",
|
|
2107
|
+
"instanceof"
|
|
2108
|
+
], node.parent.operator) || node.parent.type === "UnaryExpression" && contains(["!", "typeof"], node.parent.operator) || node.parent.type === "ConditionalExpression" && node.parent.test === node || node.parent.type === "IfStatement" && node.parent.test === node || node.parent.type === "SwitchStatement" && node.parent.discriminant === node || node.parent.type === "CallExpression" && node.parent.callee !== node && !(node.parent.parent.type === "CallExpression" && node.parent.parent.callee === node.parent) && (predicate(node.parent.callee) || tested(node.parent));
|
|
2109
|
+
}
|
|
2110
|
+
function predicate(callee) {
|
|
2111
|
+
if (callee.type === "CallExpression") return predicate(callee.callee);
|
|
2112
|
+
if (callee.type === "Identifier") return matches(/^(?:has|is)(?:[A-Z]|$)/u)(callee.name);
|
|
2113
|
+
return callee.type === "MemberExpression" && callee.property.type === "Identifier" && matches(/^(?:has|is)(?:[A-Z]|$)/u)(callee.property.name);
|
|
2114
|
+
}
|
|
2115
|
+
function deferred(input) {
|
|
2116
|
+
if (isNullish$1(input.node.parent) || input.node.parent === input.block) return false;
|
|
2117
|
+
const parent = input.node.parent;
|
|
2118
|
+
if (contains(FUNCTIONS, parent.type) && !(parent.parent?.type === "CallExpression" && some$1(parent.parent.arguments, (argument) => argument === parent))) return true;
|
|
2119
|
+
return deferred({
|
|
2120
|
+
...input,
|
|
2121
|
+
node: parent
|
|
2122
|
+
});
|
|
2123
|
+
}
|
|
2124
|
+
function needsAlias(input) {
|
|
2125
|
+
if (input.declaration.parent.type !== "BlockStatement" || !contains(FUNCTIONS, input.declaration.parent.parent.type) || !pipe(input.declaration.parent.body, takeWhile((statement) => statement !== input.declaration), every((statement) => statement.type === "VariableDeclaration"))) return true;
|
|
2126
|
+
const block = input.declaration.parent;
|
|
2127
|
+
const paths = map$1(input.reads, (read) => ({
|
|
2128
|
+
callback: enclosed({
|
|
2129
|
+
block,
|
|
2130
|
+
node: read,
|
|
2131
|
+
types: FUNCTIONS
|
|
2132
|
+
}),
|
|
2133
|
+
node: pathTop(read),
|
|
2134
|
+
text: input.context.sourceCode.getText(pathTop(read))
|
|
2135
|
+
}));
|
|
2136
|
+
return some$1(paths, (checked) => tested(checked.node) && some$1(paths, (read) => read.callback && (read.text === checked.text || startsWith(`${checked.text}.`)(read.text))));
|
|
2137
|
+
}
|
|
1711
2138
|
function enclosed(input) {
|
|
1712
2139
|
if (isNullish$1(input.node.parent) || input.node.parent === input.block) return false;
|
|
1713
|
-
if (contains
|
|
2140
|
+
if (contains(input.types, input.node.parent.type)) return true;
|
|
1714
2141
|
return enclosed({
|
|
1715
2142
|
...input,
|
|
1716
2143
|
node: input.node.parent
|
|
@@ -1729,42 +2156,31 @@ function written(node) {
|
|
|
1729
2156
|
if (node.type === "UpdateExpression" || node.type === "UnaryExpression" && node.operator === "delete") return objectRoot(node.argument);
|
|
1730
2157
|
return none();
|
|
1731
2158
|
}
|
|
1732
|
-
function inert(token) {
|
|
1733
|
-
if (contains$1([
|
|
1734
|
-
"Boolean",
|
|
1735
|
-
"Identifier",
|
|
1736
|
-
"Null",
|
|
1737
|
-
"Numeric",
|
|
1738
|
-
"String"
|
|
1739
|
-
], token.type)) return true;
|
|
1740
|
-
if (token.type === "Keyword") return contains$1(["const", "return"], token.value);
|
|
1741
|
-
return token.type === "Punctuator" && contains$1([
|
|
1742
|
-
"(",
|
|
1743
|
-
",",
|
|
1744
|
-
":",
|
|
1745
|
-
"=",
|
|
1746
|
-
"[",
|
|
1747
|
-
"{"
|
|
1748
|
-
], token.value);
|
|
1749
|
-
}
|
|
1750
2159
|
function replacement(input) {
|
|
1751
2160
|
if (input.read.parent?.type === "Property" && input.read.parent.shorthand) return `${input.name}: ${input.text}`;
|
|
1752
2161
|
return input.text;
|
|
1753
2162
|
}
|
|
1754
|
-
function report$
|
|
2163
|
+
function report$2(input) {
|
|
1755
2164
|
const reads = pipe(input.variable.references, filter$1((reference) => reference.isRead()), map$1((reference) => reference.identifier));
|
|
1756
2165
|
const text = input.context.sourceCode.getText(input.init);
|
|
1757
2166
|
pipe(rootName(input.init), filter((root) => isArrayNonEmpty(reads) && every(reads, (read) => {
|
|
1758
2167
|
const used = outermost(read);
|
|
1759
|
-
return read.parent.type !== "ExportSpecifier" && !
|
|
2168
|
+
return read.parent.type !== "ExportSpecifier" && (!needsAlias({
|
|
2169
|
+
context: input.context,
|
|
2170
|
+
declaration: input.declaration,
|
|
2171
|
+
reads
|
|
2172
|
+
}) || !enclosed({
|
|
1760
2173
|
block: input.declaration.parent,
|
|
1761
2174
|
node: read,
|
|
1762
|
-
types:
|
|
2175
|
+
types: FUNCTIONS
|
|
2176
|
+
})) && !deferred({
|
|
2177
|
+
block: input.declaration.parent,
|
|
2178
|
+
node: read
|
|
1763
2179
|
}) && !(used.parent?.type === "CallExpression" && used.parent.callee === used) && !(used.parent?.type === "TaggedTemplateExpression" && used.parent.tag === used) && !some$1(input.writes, (write) => between({
|
|
1764
2180
|
declaration: input.declaration,
|
|
1765
2181
|
node: write,
|
|
1766
2182
|
read
|
|
1767
|
-
}) && contains(written(write), root)) && sameRoot({
|
|
2183
|
+
}) && contains$1(written(write), root)) && sameRoot({
|
|
1768
2184
|
context: input.context,
|
|
1769
2185
|
declarator: input.node,
|
|
1770
2186
|
name: root,
|
|
@@ -1777,7 +2193,19 @@ function report$1(input) {
|
|
|
1777
2193
|
if (reads.length !== 1 || some$1(reads, (read) => startsWith("JSX")(read.parent.type) || enclosed({
|
|
1778
2194
|
block: input.declaration.parent,
|
|
1779
2195
|
node: read,
|
|
1780
|
-
types:
|
|
2196
|
+
types: [
|
|
2197
|
+
...FUNCTIONS,
|
|
2198
|
+
"ChainExpression",
|
|
2199
|
+
"ConditionalExpression",
|
|
2200
|
+
"DoWhileStatement",
|
|
2201
|
+
"ForInStatement",
|
|
2202
|
+
"ForOfStatement",
|
|
2203
|
+
"ForStatement",
|
|
2204
|
+
"IfStatement",
|
|
2205
|
+
"LogicalExpression",
|
|
2206
|
+
"SwitchStatement",
|
|
2207
|
+
"WhileStatement"
|
|
2208
|
+
]
|
|
1781
2209
|
}) || some$1(input.statements, (node) => between({
|
|
1782
2210
|
declaration: input.declaration,
|
|
1783
2211
|
node,
|
|
@@ -1810,7 +2238,7 @@ const noPropertyAlias = defineRule({
|
|
|
1810
2238
|
scope: context.sourceCode.getScope(candidate.node)
|
|
1811
2239
|
}), match({
|
|
1812
2240
|
onNone: constVoid,
|
|
1813
|
-
onSome: (variable) => report$
|
|
2241
|
+
onSome: (variable) => report$2({
|
|
1814
2242
|
...candidate,
|
|
1815
2243
|
context,
|
|
1816
2244
|
variable,
|
|
@@ -1823,6 +2251,19 @@ const noPropertyAlias = defineRule({
|
|
|
1823
2251
|
},
|
|
1824
2252
|
UpdateExpression: write,
|
|
1825
2253
|
VariableDeclarator: (node) => {
|
|
2254
|
+
if (node.parent.type === "VariableDeclaration" && node.parent.parent.type === "ExportNamedDeclaration" && node.id.type === "Identifier" && node.init?.type === "MemberExpression") {
|
|
2255
|
+
const init = node.init;
|
|
2256
|
+
pipe(rootName(init), flatMap((root) => variableFromScope({
|
|
2257
|
+
name: root,
|
|
2258
|
+
scope: context.sourceCode.getScope(node)
|
|
2259
|
+
})), filter((variable) => every(variable.defs, (definition) => definition.type === "Variable")), match({
|
|
2260
|
+
onNone: constVoid,
|
|
2261
|
+
onSome: (variable) => context.report({
|
|
2262
|
+
message: `Export ${variable.name} and read ${context.sourceCode.getText(init)} where it is used.`,
|
|
2263
|
+
node
|
|
2264
|
+
})
|
|
2265
|
+
}));
|
|
2266
|
+
}
|
|
1826
2267
|
if (node.parent.type === "VariableDeclaration" && node.parent.kind === "const" && node.parent.declarations.length === 1 && (node.parent.parent.type === "BlockStatement" || node.parent.parent.type === "StaticBlock") && node.id.type === "Identifier" && node.id.typeAnnotation === null && node.init?.type === "MemberExpression" && insideFunction(node.parent)) append$1(candidates, {
|
|
1827
2268
|
declaration: node.parent,
|
|
1828
2269
|
init: node.init,
|
|
@@ -1846,7 +2287,7 @@ function isTopLevelStatement(node) {
|
|
|
1846
2287
|
}
|
|
1847
2288
|
function insideCycleType(input) {
|
|
1848
2289
|
if (input.node.type === "Program") return false;
|
|
1849
|
-
if (input.node.type === "TSTypeAliasDeclaration" && isTopLevelStatement(input.node)) return contains
|
|
2290
|
+
if (input.node.type === "TSTypeAliasDeclaration" && isTopLevelStatement(input.node)) return contains(input.cycleNames, input.node.id.name);
|
|
1850
2291
|
return insideCycleType({
|
|
1851
2292
|
cycleNames: input.cycleNames,
|
|
1852
2293
|
node: input.node.parent
|
|
@@ -1887,7 +2328,7 @@ function shapeLiterals(input) {
|
|
|
1887
2328
|
return pipe(typeDeclarations({
|
|
1888
2329
|
context: input.context,
|
|
1889
2330
|
name: input.type.typeName
|
|
1890
|
-
}), filter$1((declaration) => declaration.type === "TSTypeAliasDeclaration"), filter$1((alias) => !contains
|
|
2331
|
+
}), filter$1((declaration) => declaration.type === "TSTypeAliasDeclaration"), filter$1((alias) => !contains(input.seen, alias)), flatMap$1((alias) => shapeLiterals({
|
|
1891
2332
|
...input,
|
|
1892
2333
|
seen: append(input.seen, alias),
|
|
1893
2334
|
type: alias.typeAnnotation
|
|
@@ -2035,7 +2476,7 @@ function isReactUseRef(input) {
|
|
|
2035
2476
|
node: input.node.callee,
|
|
2036
2477
|
source: "react"
|
|
2037
2478
|
});
|
|
2038
|
-
return input.node.callee.type === "MemberExpression" && contains(memberName(input.node.callee), "useRef") && input.node.callee.object.type === "Identifier" && isNamespaceImport({
|
|
2479
|
+
return input.node.callee.type === "MemberExpression" && contains$1(memberName(input.node.callee), "useRef") && input.node.callee.object.type === "Identifier" && isNamespaceImport({
|
|
2039
2480
|
context: input.context,
|
|
2040
2481
|
node: input.node.callee.object,
|
|
2041
2482
|
source: "react"
|
|
@@ -2110,7 +2551,7 @@ const shipped = [
|
|
|
2110
2551
|
schema: "Natural"
|
|
2111
2552
|
}
|
|
2112
2553
|
];
|
|
2113
|
-
function schemaMember(input) {
|
|
2554
|
+
function schemaMember$1(input) {
|
|
2114
2555
|
if (input.node.type !== "MemberExpression" || !importedMember({
|
|
2115
2556
|
context: input.context,
|
|
2116
2557
|
importedName: "Schema",
|
|
@@ -2120,16 +2561,16 @@ function schemaMember(input) {
|
|
|
2120
2561
|
}
|
|
2121
2562
|
function shippedSchema(input) {
|
|
2122
2563
|
if (input.filter.type !== "CallExpression") return none();
|
|
2123
|
-
const base = schemaMember({
|
|
2564
|
+
const base = schemaMember$1({
|
|
2124
2565
|
context: input.context,
|
|
2125
2566
|
node: input.base
|
|
2126
2567
|
});
|
|
2127
|
-
const check = schemaMember({
|
|
2568
|
+
const check = schemaMember$1({
|
|
2128
2569
|
context: input.context,
|
|
2129
2570
|
node: input.filter.callee
|
|
2130
2571
|
});
|
|
2131
2572
|
const [value, ...rest] = input.filter.arguments;
|
|
2132
|
-
return pipe(shipped, findFirst((entry) => contains(base, entry.base) && contains(check, entry.check) && isArrayEmpty(rest) && match(fromUndefinedOr(entry.only), {
|
|
2573
|
+
return pipe(shipped, findFirst((entry) => contains$1(base, entry.base) && contains$1(check, entry.check) && isArrayEmpty(rest) && match(fromUndefinedOr(entry.only), {
|
|
2133
2574
|
onNone: () => value === void 0,
|
|
2134
2575
|
onSome: (only) => value?.type === "Literal" && value.value === only
|
|
2135
2576
|
})), map((entry) => entry.schema));
|
|
@@ -2140,14 +2581,14 @@ function pipedBase(node) {
|
|
|
2140
2581
|
if (node.parent.arguments[1] !== node) return none();
|
|
2141
2582
|
return fromNullishOr(node.parent.arguments[0]);
|
|
2142
2583
|
}
|
|
2143
|
-
if (node.parent.callee.type !== "MemberExpression" || !contains(memberName(node.parent.callee), "pipe") || node.parent.arguments.length !== 1) return none();
|
|
2584
|
+
if (node.parent.callee.type !== "MemberExpression" || !contains$1(memberName(node.parent.callee), "pipe") || node.parent.arguments.length !== 1) return none();
|
|
2144
2585
|
return some(node.parent.callee.object);
|
|
2145
2586
|
}
|
|
2146
2587
|
function reinventedSchema(input) {
|
|
2147
|
-
if (input.node.callee.type !== "MemberExpression" || !contains(memberName(input.node.callee), "check")) return none();
|
|
2588
|
+
if (input.node.callee.type !== "MemberExpression" || !contains$1(memberName(input.node.callee), "check")) return none();
|
|
2148
2589
|
const callee = input.node.callee;
|
|
2149
2590
|
const [filter] = input.node.arguments;
|
|
2150
|
-
if (isSome(schemaMember({
|
|
2591
|
+
if (isSome(schemaMember$1({
|
|
2151
2592
|
context: input.context,
|
|
2152
2593
|
node: callee
|
|
2153
2594
|
}))) {
|
|
@@ -2267,7 +2708,7 @@ const noReturnType = defineRule({
|
|
|
2267
2708
|
});
|
|
2268
2709
|
//#endregion
|
|
2269
2710
|
//#region src/rules/no-single-use-value.ts
|
|
2270
|
-
const
|
|
2711
|
+
const REPEATING = [
|
|
2271
2712
|
"ArrowFunctionExpression",
|
|
2272
2713
|
"DoWhileStatement",
|
|
2273
2714
|
"ForInStatement",
|
|
@@ -2282,7 +2723,7 @@ function runsOnce(input) {
|
|
|
2282
2723
|
...input,
|
|
2283
2724
|
node: input.node
|
|
2284
2725
|
});
|
|
2285
|
-
if (contains
|
|
2726
|
+
if (contains(REPEATING, input.node.type) || input.node.type === "IfStatement" || input.node.type === "ConditionalExpression" || input.node.type === "LogicalExpression" || input.node.type === "ChainExpression" || input.node.type === "SwitchStatement" || input.node.type === "TryStatement") return false;
|
|
2286
2727
|
return climb({
|
|
2287
2728
|
...input,
|
|
2288
2729
|
node: input.node
|
|
@@ -2306,7 +2747,7 @@ function insideConditional(input) {
|
|
|
2306
2747
|
});
|
|
2307
2748
|
}
|
|
2308
2749
|
function candidate(node) {
|
|
2309
|
-
return node.parent.type === "VariableDeclaration" && node.parent.kind === "const" && node.parent.declarations.length === 1 && node.parent.parent.type === "BlockStatement" && node.id.type === "Identifier" && node.id.typeAnnotation === null && node.init !== null && !contains
|
|
2750
|
+
return node.parent.type === "VariableDeclaration" && node.parent.kind === "const" && node.parent.declarations.length === 1 && node.parent.parent.type === "BlockStatement" && node.id.type === "Identifier" && node.id.typeAnnotation === null && node.init !== null && !contains(["ArrowFunctionExpression", "FunctionExpression"], node.init.type) && !(node.init.type === "MemberExpression" && isSome(rootName(node.init))) && !(node.init.type === "Literal" && !hasProperty(node.init, "regex")) && !(node.init.type === "TemplateLiteral" && isArrayEmpty(node.init.expressions));
|
|
2310
2751
|
}
|
|
2311
2752
|
function unwrapped(node) {
|
|
2312
2753
|
return pipe(typeWrapped(node), match({
|
|
@@ -2324,10 +2765,61 @@ function inlined(input) {
|
|
|
2324
2765
|
if (input.read.range[0] === input.next.range[0]) return `;${value}`;
|
|
2325
2766
|
return value;
|
|
2326
2767
|
}
|
|
2327
|
-
function
|
|
2768
|
+
function executes(node) {
|
|
2769
|
+
if (contains([
|
|
2770
|
+
"AwaitExpression",
|
|
2771
|
+
"CallExpression",
|
|
2772
|
+
"MemberExpression",
|
|
2773
|
+
"NewExpression",
|
|
2774
|
+
"SpreadElement",
|
|
2775
|
+
"TaggedTemplateExpression",
|
|
2776
|
+
"YieldExpression"
|
|
2777
|
+
], node.type)) return true;
|
|
2778
|
+
if (contains([
|
|
2779
|
+
"ArrowFunctionExpression",
|
|
2780
|
+
"FunctionDeclaration",
|
|
2781
|
+
"FunctionExpression"
|
|
2782
|
+
], node.type)) return false;
|
|
2783
|
+
return some$1(childNodes(node), executes);
|
|
2784
|
+
}
|
|
2785
|
+
function suspends(node) {
|
|
2786
|
+
if (node.type === "YieldExpression" || node.type === "AwaitExpression") return true;
|
|
2787
|
+
if (contains([
|
|
2788
|
+
"ArrowFunctionExpression",
|
|
2789
|
+
"FunctionDeclaration",
|
|
2790
|
+
"FunctionExpression"
|
|
2791
|
+
], node.type)) return false;
|
|
2792
|
+
return some$1(childNodes(node), suspends);
|
|
2793
|
+
}
|
|
2794
|
+
function surface(node) {
|
|
2795
|
+
if (contains([
|
|
2796
|
+
"ExpressionStatement",
|
|
2797
|
+
"ReturnStatement",
|
|
2798
|
+
"VariableDeclaration"
|
|
2799
|
+
], node.type) || node.type === "ArrowFunctionExpression" && node.expression) return true;
|
|
2800
|
+
if (isNullish$1(node.parent) || !contains([
|
|
2801
|
+
"AwaitExpression",
|
|
2802
|
+
"JSXAttribute",
|
|
2803
|
+
"JSXElement",
|
|
2804
|
+
"JSXExpressionContainer",
|
|
2805
|
+
"JSXFragment",
|
|
2806
|
+
"JSXOpeningElement",
|
|
2807
|
+
"VariableDeclarator",
|
|
2808
|
+
"YieldExpression"
|
|
2809
|
+
], node.type)) return false;
|
|
2810
|
+
return surface(node.parent);
|
|
2811
|
+
}
|
|
2812
|
+
function readable(input) {
|
|
2813
|
+
if (!pipe(input.context.sourceCode.getText(input.init), includes("\n"))) return true;
|
|
2814
|
+
const slot = outermost(input.read);
|
|
2815
|
+
if (isNullish$1(slot.parent) || suspends(input.init)) return false;
|
|
2816
|
+
if (slot.parent.type === "JSXExpressionContainer" || slot.parent.type === "ReturnStatement") return true;
|
|
2817
|
+
return isNotNullish(slot.parent.parent) && (slot.parent.type === "CallExpression" && slot.parent.callee !== slot || slot.parent.type === "ArrayExpression") && surface(slot.parent.parent);
|
|
2818
|
+
}
|
|
2819
|
+
function report$1(input) {
|
|
2328
2820
|
const declaration = input.node.parent;
|
|
2329
2821
|
const init = input.node.init;
|
|
2330
|
-
if (declaration.type !== "VariableDeclaration" || declaration.parent.type !== "BlockStatement" || input.node.id.type !== "Identifier" || init === null
|
|
2822
|
+
if (declaration.type !== "VariableDeclaration" || declaration.parent.type !== "BlockStatement" || input.node.id.type !== "Identifier" || init === null) return;
|
|
2331
2823
|
const statements = declaration.parent.body;
|
|
2332
2824
|
const name = input.node.id.name;
|
|
2333
2825
|
pipe(findFirstIndex(statements, (statement) => statement === declaration), flatMap((index) => get$1(statements, index + 1)), flatMap((next) => pipe(variableFromScope({
|
|
@@ -2336,6 +2828,10 @@ function report(input) {
|
|
|
2336
2828
|
}), map((variable) => filter$1(variable.references, (reference) => reference.isRead())), filter((reads) => reads.length === 1), flatMap(head), map((reference) => reference.identifier), filter((read) => read.range[0] >= next.range[0] && read.range[1] <= next.range[1] && climb({
|
|
2337
2829
|
node: read,
|
|
2338
2830
|
statement: next
|
|
2831
|
+
}) && readable({
|
|
2832
|
+
context: input.context,
|
|
2833
|
+
init,
|
|
2834
|
+
read
|
|
2339
2835
|
}) && !(init.type === "ConditionalExpression" && insideConditional({
|
|
2340
2836
|
node: read,
|
|
2341
2837
|
statement: next
|
|
@@ -2349,7 +2845,7 @@ function report(input) {
|
|
|
2349
2845
|
if (!fixable({
|
|
2350
2846
|
init,
|
|
2351
2847
|
read: found.read
|
|
2352
|
-
})) return [];
|
|
2848
|
+
}) || executes(init) && !every(input.context.sourceCode.getTokensBetween(declaration, found.read), inert)) return [];
|
|
2353
2849
|
return [fixer.remove(declaration), fixer.replaceText(found.read, inlined({
|
|
2354
2850
|
context: input.context,
|
|
2355
2851
|
init,
|
|
@@ -2375,7 +2871,7 @@ const noSingleUseValue = defineRule({
|
|
|
2375
2871
|
CallExpression: effect,
|
|
2376
2872
|
NewExpression: effect,
|
|
2377
2873
|
"Program:exit": () => {
|
|
2378
|
-
for (const node of get$3(candidates)) report({
|
|
2874
|
+
for (const node of get$3(candidates)) report$1({
|
|
2379
2875
|
context,
|
|
2380
2876
|
effects: get$3(effects),
|
|
2381
2877
|
node
|
|
@@ -2395,6 +2891,22 @@ const noSingleUseValue = defineRule({
|
|
|
2395
2891
|
}
|
|
2396
2892
|
});
|
|
2397
2893
|
//#endregion
|
|
2894
|
+
//#region src/rules/no-stored-jsx.ts
|
|
2895
|
+
function element(node) {
|
|
2896
|
+
if (node.type === "JSXElement" || node.type === "JSXFragment") return true;
|
|
2897
|
+
return pipe(typeWrapped(node), exists(element));
|
|
2898
|
+
}
|
|
2899
|
+
const noStoredJsx = defineRule({
|
|
2900
|
+
create: (context) => ({ VariableDeclarator: (node) => {
|
|
2901
|
+
if (node.init === null || !element(node.init)) return;
|
|
2902
|
+
context.report({
|
|
2903
|
+
message: "Write the element where it renders, or make it a component when several places render it.",
|
|
2904
|
+
node
|
|
2905
|
+
});
|
|
2906
|
+
} }),
|
|
2907
|
+
meta: { type: "problem" }
|
|
2908
|
+
});
|
|
2909
|
+
//#endregion
|
|
2398
2910
|
//#region src/rules/no-stored-schema-operation.ts
|
|
2399
2911
|
function storedSchemaOperation(node) {
|
|
2400
2912
|
return node.parent.type === "VariableDeclarator" && node.parent.init === node || node.parent.type === "Property" && node.parent.value === node || node.parent.type === "PropertyDefinition" && node.parent.value === node || node.parent.type === "ArrayExpression" || node.parent.type === "AssignmentExpression" && node.parent.right === node;
|
|
@@ -2458,7 +2970,7 @@ function detachable(input) {
|
|
|
2458
2970
|
function forwardedChain(input) {
|
|
2459
2971
|
const chain = callGroups(input.returned);
|
|
2460
2972
|
function fixed(node) {
|
|
2461
|
-
return node.type === "Literal" || exists(rootName(node), (root) => !contains
|
|
2973
|
+
return node.type === "Literal" || exists(rootName(node), (root) => !contains(input.names, root));
|
|
2462
2974
|
}
|
|
2463
2975
|
return matchRight(chain.groups, {
|
|
2464
2976
|
onEmpty: constFalse,
|
|
@@ -2532,7 +3044,7 @@ function exportedAlias(input) {
|
|
|
2532
3044
|
return input.node.parent.type === "VariableDeclaration" && input.node.parent.kind === "const" && input.node.parent.parent.type === "ExportNamedDeclaration" && input.node.id.type === "Identifier" && input.node.init?.type === "Identifier" && pipe(variableFor(input.context, input.node.init), exists((variable) => some$1(variable.defs, (definition) => definition.type === "ImportBinding" || definition.type === "FunctionName" || definition.type === "ClassName" || definition.node.type === "VariableDeclarator" && definition.node.parent.type === "VariableDeclaration" && definition.node.parent.kind === "const")));
|
|
2533
3045
|
}
|
|
2534
3046
|
function singleUseLiteral(input) {
|
|
2535
|
-
if (input.node.parent.type !== "VariableDeclaration" || input.node.parent.kind !== "const" || isExport(input.node.parent.parent) || input.node.id.type !== "Identifier" || input.node.id.typeAnnotation !== null || !(input.node.init?.type === "Literal" && !hasProperty(input.node.init, "regex") || input.node.init?.type === "TemplateLiteral" && isArrayEmpty(input.node.init.expressions))) return false;
|
|
3047
|
+
if (input.node.parent.type !== "VariableDeclaration" || input.node.parent.kind !== "const" || isExport(input.node.parent.parent) || input.node.parent.parent.type === "Program" || input.node.id.type !== "Identifier" || input.node.id.typeAnnotation !== null || !(input.node.init?.type === "Literal" && !hasProperty(input.node.init, "regex") || input.node.init?.type === "TemplateLiteral" && isArrayEmpty(input.node.init.expressions))) return false;
|
|
2536
3048
|
return pipe(variableFromScope({
|
|
2537
3049
|
name: input.node.id.name,
|
|
2538
3050
|
scope: input.context.sourceCode.getScope(input.node)
|
|
@@ -2547,7 +3059,7 @@ function singleUseLayerPart(input) {
|
|
|
2547
3059
|
}), map((variable) => filter$1(variable.references, (reference) => reference.isRead())), exists((reads) => {
|
|
2548
3060
|
const consumer = reads[0]?.identifier.parent;
|
|
2549
3061
|
if (reads.length !== 1 || consumer?.type !== "CallExpression") return false;
|
|
2550
|
-
if (init.type === "MemberExpression") return contains(memberName(init), "layer") && (importedMember({
|
|
3062
|
+
if (init.type === "MemberExpression") return contains$1(memberName(init), "layer") && (importedMember({
|
|
2551
3063
|
context: input.context,
|
|
2552
3064
|
importedName: "Layer",
|
|
2553
3065
|
node: consumer.callee
|
|
@@ -2660,6 +3172,45 @@ const noTrivialIndirection = defineRule({
|
|
|
2660
3172
|
meta: { type: "problem" }
|
|
2661
3173
|
});
|
|
2662
3174
|
//#endregion
|
|
3175
|
+
//#region src/rules/no-type-from-value.ts
|
|
3176
|
+
function schemaMember(name) {
|
|
3177
|
+
return name.type === "TSQualifiedName" && contains([
|
|
3178
|
+
"Encoded",
|
|
3179
|
+
"Type",
|
|
3180
|
+
"cases",
|
|
3181
|
+
"fields",
|
|
3182
|
+
"literals",
|
|
3183
|
+
"members"
|
|
3184
|
+
], name.right.name);
|
|
3185
|
+
}
|
|
3186
|
+
function valueQuery(node) {
|
|
3187
|
+
if (node.type === "TSParenthesizedType") return valueQuery(node.typeAnnotation);
|
|
3188
|
+
if (node.type === "TSIndexedAccessType") return valueQuery(node.objectType);
|
|
3189
|
+
return node.type === "TSTypeQuery" && (node.exprName.type === "Identifier" || node.exprName.type === "TSQualifiedName" && !schemaMember(node.exprName));
|
|
3190
|
+
}
|
|
3191
|
+
function nestedObject(node) {
|
|
3192
|
+
return node.parent?.type === "TSIndexedAccessType" && node.parent.objectType === node;
|
|
3193
|
+
}
|
|
3194
|
+
const noTypeFromValue = defineRule({
|
|
3195
|
+
create: (context) => {
|
|
3196
|
+
function report(node) {
|
|
3197
|
+
context.report({
|
|
3198
|
+
message: "Declare the closed set as a schema, such as Schema.Literals, and read its type with typeof X.Type.",
|
|
3199
|
+
node
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
return {
|
|
3203
|
+
TSIndexedAccessType: (node) => {
|
|
3204
|
+
if (!nestedObject(node) && valueQuery(node.objectType)) report(node);
|
|
3205
|
+
},
|
|
3206
|
+
TSTypeOperator: (node) => {
|
|
3207
|
+
if (node.operator === "keyof" && !nestedObject(node) && valueQuery(node.typeAnnotation)) report(node);
|
|
3208
|
+
}
|
|
3209
|
+
};
|
|
3210
|
+
},
|
|
3211
|
+
meta: { type: "problem" }
|
|
3212
|
+
});
|
|
3213
|
+
//#endregion
|
|
2663
3214
|
//#region src/rules/no-typeof.ts
|
|
2664
3215
|
const noTypeof = defineRule({
|
|
2665
3216
|
create: (context) => ({ UnaryExpression: (node) => {
|
|
@@ -2741,6 +3292,44 @@ const noUnvalidatedJsonDecode = defineRule({
|
|
|
2741
3292
|
meta: { type: "problem" }
|
|
2742
3293
|
});
|
|
2743
3294
|
//#endregion
|
|
3295
|
+
//#region src/rules/preserve-error-cause.ts
|
|
3296
|
+
function constructedFields(node) {
|
|
3297
|
+
if (node?.type !== "CallExpression" && node?.type !== "NewExpression") return none();
|
|
3298
|
+
const constructs = node.type === "NewExpression" || node.callee.type === "MemberExpression" && contains$1(memberName(node.callee), "make");
|
|
3299
|
+
const fields = node.arguments[0];
|
|
3300
|
+
if (!constructs || fields?.type !== "ObjectExpression") return none();
|
|
3301
|
+
return some(fields);
|
|
3302
|
+
}
|
|
3303
|
+
function dropsCause(node) {
|
|
3304
|
+
if (node.type !== "ArrowFunctionExpression" && node.type !== "FunctionExpression" || isArrayEmpty(node.params)) return false;
|
|
3305
|
+
return exists(constructedFields(returnedExpression(node)), (fields) => every(fields.properties, (property) => property.type === "SpreadElement" || !(property.key.type === "Identifier" && property.key.name === "cause")));
|
|
3306
|
+
}
|
|
3307
|
+
function report(context, node) {
|
|
3308
|
+
context.report({
|
|
3309
|
+
message: "Keep the original failure as the new error’s cause: X.make({cause, message}).",
|
|
3310
|
+
node
|
|
3311
|
+
});
|
|
3312
|
+
}
|
|
3313
|
+
const preserveErrorCause = defineRule({
|
|
3314
|
+
create: (context) => ({ CallExpression: (node) => {
|
|
3315
|
+
if (node.callee.type !== "MemberExpression" || !importedMember({
|
|
3316
|
+
context,
|
|
3317
|
+
importedName: "Effect",
|
|
3318
|
+
node: node.callee
|
|
3319
|
+
})) return;
|
|
3320
|
+
const name = memberName(node.callee);
|
|
3321
|
+
if (contains$1(name, "mapError")) {
|
|
3322
|
+
forEach(filter$1(node.arguments, dropsCause), (callback) => report(context, callback));
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3325
|
+
if (!exists(name, (method) => method === "try" || method === "tryPromise")) return;
|
|
3326
|
+
const options = node.arguments[0];
|
|
3327
|
+
if (options?.type !== "ObjectExpression") return;
|
|
3328
|
+
for (const property of options.properties) if (property.type === "Property" && property.key.type === "Identifier" && property.key.name === "catch" && dropsCause(property.value)) report(context, property.value);
|
|
3329
|
+
} }),
|
|
3330
|
+
meta: { type: "problem" }
|
|
3331
|
+
});
|
|
3332
|
+
//#endregion
|
|
2744
3333
|
//#region src/rules/schema-type-pair.ts
|
|
2745
3334
|
function expressionRoot(node) {
|
|
2746
3335
|
if (node.type === "MemberExpression") return expressionRoot(node.object);
|
|
@@ -2790,7 +3379,7 @@ function isSchemaDefinition(input) {
|
|
|
2790
3379
|
context: input.context,
|
|
2791
3380
|
importedName: "Schema",
|
|
2792
3381
|
node: input.node.callee
|
|
2793
|
-
}) && contains(memberName(input.node.callee), "make")) return false;
|
|
3382
|
+
}) && contains$1(memberName(input.node.callee), "make")) return false;
|
|
2794
3383
|
if (!expressionUsesImport({
|
|
2795
3384
|
context: input.context,
|
|
2796
3385
|
importedName: "Schema",
|
|
@@ -2808,6 +3397,31 @@ function isSchemaDefinition(input) {
|
|
|
2808
3397
|
function namedTypeAlias(input) {
|
|
2809
3398
|
return pipe(input.typeStatement, flatMap(typeAlias), filter((declaration) => declaration.id.name === input.name));
|
|
2810
3399
|
}
|
|
3400
|
+
function exportedNames(input) {
|
|
3401
|
+
return flatMap$1(input.program.body, (statement) => {
|
|
3402
|
+
if (statement.type !== "ExportNamedDeclaration" || statement.declaration !== null || statement.source !== null) return empty();
|
|
3403
|
+
return flatMap$1(statement.specifiers, (specifier) => {
|
|
3404
|
+
const typeOnly = statement.exportKind === "type" || specifier.exportKind === "type";
|
|
3405
|
+
if (specifier.local.type !== "Identifier" || typeOnly !== input.typeOnly) return empty();
|
|
3406
|
+
return [specifier.local.name];
|
|
3407
|
+
});
|
|
3408
|
+
});
|
|
3409
|
+
}
|
|
3410
|
+
function exportLists(program) {
|
|
3411
|
+
return {
|
|
3412
|
+
types: exportedNames({
|
|
3413
|
+
program,
|
|
3414
|
+
typeOnly: true
|
|
3415
|
+
}),
|
|
3416
|
+
values: exportedNames({
|
|
3417
|
+
program,
|
|
3418
|
+
typeOnly: false
|
|
3419
|
+
})
|
|
3420
|
+
};
|
|
3421
|
+
}
|
|
3422
|
+
function isExported(input) {
|
|
3423
|
+
return input.statement.type === "ExportNamedDeclaration" || contains(input.exports.values, input.name) || input.typeOnly && contains(input.exports.types, input.name);
|
|
3424
|
+
}
|
|
2811
3425
|
function reportSchemaVariable(input) {
|
|
2812
3426
|
if (input.variable.id.type !== "Identifier" || !matches(/^[A-Z]/u)(input.variable.id.name)) return;
|
|
2813
3427
|
if (input.variable.init === null || !isSchemaDefinition({
|
|
@@ -2816,7 +3430,7 @@ function reportSchemaVariable(input) {
|
|
|
2816
3430
|
})) return;
|
|
2817
3431
|
const name = input.variable.id.name;
|
|
2818
3432
|
const typeStatement = pipe(input.program.body, findFirstIndex((statement) => statement === input.statement), filter((index) => index > 0), flatMap((index) => get$1(input.program.body, index - 1)));
|
|
2819
|
-
if (contains
|
|
3433
|
+
if (contains(input.cycleNames, name)) {
|
|
2820
3434
|
if (!pipe(namedTypeAlias({
|
|
2821
3435
|
name,
|
|
2822
3436
|
typeStatement
|
|
@@ -2843,6 +3457,20 @@ function reportSchemaVariable(input) {
|
|
|
2843
3457
|
message: `Place \`type ${name} = typeof ${name}.Type\` immediately before this Schema.`,
|
|
2844
3458
|
node: input.variable
|
|
2845
3459
|
});
|
|
3460
|
+
else if (isExported({
|
|
3461
|
+
exports: input.exports,
|
|
3462
|
+
name,
|
|
3463
|
+
statement: input.statement,
|
|
3464
|
+
typeOnly: false
|
|
3465
|
+
}) !== exists(typeStatement, (statement) => isExported({
|
|
3466
|
+
exports: input.exports,
|
|
3467
|
+
name,
|
|
3468
|
+
statement,
|
|
3469
|
+
typeOnly: true
|
|
3470
|
+
}))) input.context.report({
|
|
3471
|
+
message: `Export both \`type ${name}\` and the \`${name}\` Schema, or neither.`,
|
|
3472
|
+
node: input.variable
|
|
3473
|
+
});
|
|
2846
3474
|
if (input.variable.init.type === "TSSatisfiesExpression" && schemaSchemaType({
|
|
2847
3475
|
context: input.context,
|
|
2848
3476
|
node: input.variable.init.typeAnnotation
|
|
@@ -2864,11 +3492,13 @@ const schemaTypePair = defineRule({
|
|
|
2864
3492
|
context,
|
|
2865
3493
|
program
|
|
2866
3494
|
});
|
|
3495
|
+
const exports = exportLists(program);
|
|
2867
3496
|
for (const statement of program.body) {
|
|
2868
3497
|
const declaration = statementDeclaration(statement);
|
|
2869
3498
|
if (declaration?.type === "VariableDeclaration") for (const variable of declaration.declarations) reportSchemaVariable({
|
|
2870
3499
|
context,
|
|
2871
3500
|
cycleNames,
|
|
3501
|
+
exports,
|
|
2872
3502
|
program,
|
|
2873
3503
|
statement,
|
|
2874
3504
|
variable
|
|
@@ -2879,7 +3509,7 @@ const schemaTypePair = defineRule({
|
|
|
2879
3509
|
});
|
|
2880
3510
|
//#endregion
|
|
2881
3511
|
//#region src/oxlint.ts
|
|
2882
|
-
const
|
|
3512
|
+
const REACT_LEGACY_APIS = [
|
|
2883
3513
|
"Component",
|
|
2884
3514
|
"PureComponent",
|
|
2885
3515
|
"createRef",
|
|
@@ -2888,9 +3518,9 @@ const reactLegacyApis = [
|
|
|
2888
3518
|
"useCallback",
|
|
2889
3519
|
"useMemo"
|
|
2890
3520
|
];
|
|
2891
|
-
const
|
|
2892
|
-
const
|
|
2893
|
-
const
|
|
3521
|
+
const REACT_LEGACY_MESSAGE = "Use React 19 function components with refs as props; React Compiler owns memoization.";
|
|
3522
|
+
const MAP_MESSAGE = "Use HashMap, or MutableHashMap for local mutation; for identity keys, wrap objects the code owns in Equal.byReferenceUnsafe, which marks them reference-compared globally.";
|
|
3523
|
+
const REACT_HOOKS = [
|
|
2894
3524
|
"use",
|
|
2895
3525
|
"useActionState",
|
|
2896
3526
|
"useContext",
|
|
@@ -2909,8 +3539,8 @@ const reactHooks = [
|
|
|
2909
3539
|
"useSyncExternalStore",
|
|
2910
3540
|
"useTransition"
|
|
2911
3541
|
];
|
|
2912
|
-
const
|
|
2913
|
-
const
|
|
3542
|
+
const RUN_MESSAGE = "Run the entry point with NodeRuntime.runMain, and Effects in React through atoms. A Promise that an external contract demands takes Effect.runPromise once, with an inline disable that names the contract.";
|
|
3543
|
+
const RUNNERS = [
|
|
2914
3544
|
"runCallback",
|
|
2915
3545
|
"runCallbackWith",
|
|
2916
3546
|
"runFork",
|
|
@@ -2924,7 +3554,7 @@ const runners = [
|
|
|
2924
3554
|
"runSyncExitWith",
|
|
2925
3555
|
"runSyncWith"
|
|
2926
3556
|
];
|
|
2927
|
-
const
|
|
3557
|
+
const SET_MESSAGE = "Use HashSet, or MutableHashSet for local mutation; for identity keys, wrap objects the code owns in Equal.byReferenceUnsafe, which marks them reference-compared globally.";
|
|
2928
3558
|
const oxlint = defineConfig({
|
|
2929
3559
|
categories: {
|
|
2930
3560
|
correctness: "off",
|
|
@@ -2969,6 +3599,10 @@ const oxlint = defineConfig({
|
|
|
2969
3599
|
],
|
|
2970
3600
|
rules: { "import/no-default-export": "off" }
|
|
2971
3601
|
},
|
|
3602
|
+
{
|
|
3603
|
+
files: ["**/app.plugin.js"],
|
|
3604
|
+
rules: { "@deslop/coding-standards/no-barrel-module": "off" }
|
|
3605
|
+
},
|
|
2972
3606
|
{
|
|
2973
3607
|
files: ["**/components/ui/**"],
|
|
2974
3608
|
rules: {
|
|
@@ -2981,7 +3615,9 @@ const oxlint = defineConfig({
|
|
|
2981
3615
|
rules: {
|
|
2982
3616
|
"@deslop/coding-standards/no-destructured-parameter": "off",
|
|
2983
3617
|
"@deslop/coding-standards/no-native-control": "off",
|
|
2984
|
-
"@deslop/coding-standards/no-renamed-import": "off"
|
|
3618
|
+
"@deslop/coding-standards/no-renamed-import": "off",
|
|
3619
|
+
"jsx-a11y/click-events-have-key-events": "off",
|
|
3620
|
+
"jsx-a11y/label-has-associated-control": "off"
|
|
2985
3621
|
}
|
|
2986
3622
|
},
|
|
2987
3623
|
{
|
|
@@ -3008,19 +3644,29 @@ const oxlint = defineConfig({
|
|
|
3008
3644
|
"typescript",
|
|
3009
3645
|
"oxc",
|
|
3010
3646
|
"import",
|
|
3647
|
+
"jsx-a11y",
|
|
3011
3648
|
"react",
|
|
3012
3649
|
"unicorn"
|
|
3013
3650
|
],
|
|
3014
3651
|
rules: {
|
|
3652
|
+
"@deslop/coding-standards/explicit-effect-concurrency": "error",
|
|
3653
|
+
"@deslop/coding-standards/module-constant": "error",
|
|
3654
|
+
"@deslop/coding-standards/motion-limits": "error",
|
|
3015
3655
|
"@deslop/coding-standards/no-array-wrap-ternary": "error",
|
|
3656
|
+
"@deslop/coding-standards/no-barrel-module": "error",
|
|
3657
|
+
"@deslop/coding-standards/no-compiler-opt-out": "error",
|
|
3658
|
+
"@deslop/coding-standards/no-component-atom-allocation": "error",
|
|
3016
3659
|
"@deslop/coding-standards/no-constant-function": "error",
|
|
3017
3660
|
"@deslop/coding-standards/no-deep-pipe": "error",
|
|
3018
3661
|
"@deslop/coding-standards/no-destructured-parameter": "error",
|
|
3019
3662
|
"@deslop/coding-standards/no-discarded-failure": "error",
|
|
3020
3663
|
"@deslop/coding-standards/no-double-nullish-check": "error",
|
|
3021
3664
|
"@deslop/coding-standards/no-effect-property-arrow": "error",
|
|
3665
|
+
"@deslop/coding-standards/no-empty-recovery": "error",
|
|
3022
3666
|
"@deslop/coding-standards/no-error-message-assertion": "error",
|
|
3023
3667
|
"@deslop/coding-standards/no-fake-ref-state": "error",
|
|
3668
|
+
"@deslop/coding-standards/no-floating-derived-type": "error",
|
|
3669
|
+
"@deslop/coding-standards/no-gradient": "error",
|
|
3024
3670
|
"@deslop/coding-standards/no-hand-written-guard": "error",
|
|
3025
3671
|
"@deslop/coding-standards/no-in-operator": "error",
|
|
3026
3672
|
"@deslop/coding-standards/no-let": "error",
|
|
@@ -3039,13 +3685,16 @@ const oxlint = defineConfig({
|
|
|
3039
3685
|
"@deslop/coding-standards/no-renamed-import": "error",
|
|
3040
3686
|
"@deslop/coding-standards/no-return-type": "error",
|
|
3041
3687
|
"@deslop/coding-standards/no-single-use-value": "error",
|
|
3688
|
+
"@deslop/coding-standards/no-stored-jsx": "error",
|
|
3042
3689
|
"@deslop/coding-standards/no-stored-schema-operation": "error",
|
|
3043
3690
|
"@deslop/coding-standards/no-switch-statement": "error",
|
|
3044
3691
|
"@deslop/coding-standards/no-trivial-indirection": "error",
|
|
3692
|
+
"@deslop/coding-standards/no-type-from-value": "error",
|
|
3045
3693
|
"@deslop/coding-standards/no-typeof": "error",
|
|
3046
3694
|
"@deslop/coding-standards/no-undestructured-use-state": "error",
|
|
3047
3695
|
"@deslop/coding-standards/no-unexplained-disable": "error",
|
|
3048
3696
|
"@deslop/coding-standards/no-unvalidated-json-decode": "error",
|
|
3697
|
+
"@deslop/coding-standards/preserve-error-cause": "error",
|
|
3049
3698
|
"@deslop/coding-standards/schema-type-pair": "error",
|
|
3050
3699
|
"effecttsgo/abort-controller-in-effect": "error",
|
|
3051
3700
|
"effecttsgo/crypto-random-uuid": "error",
|
|
@@ -3178,7 +3827,7 @@ const oxlint = defineConfig({
|
|
|
3178
3827
|
"typescript/no-import-type-side-effects": "error",
|
|
3179
3828
|
"typescript/no-inferrable-types": "error",
|
|
3180
3829
|
"typescript/no-invalid-void-type": "error",
|
|
3181
|
-
"typescript/no-misused-promises": ["error", { checksVoidReturn: false }],
|
|
3830
|
+
"typescript/no-misused-promises": ["error", { checksVoidReturn: { attributes: false } }],
|
|
3182
3831
|
"typescript/no-misused-spread": "error",
|
|
3183
3832
|
"typescript/no-namespace": ["error", { allowDeclarations: true }],
|
|
3184
3833
|
"typescript/no-non-null-assertion": "error",
|
|
@@ -3210,6 +3859,7 @@ const oxlint = defineConfig({
|
|
|
3210
3859
|
"typescript/restrict-plus-operands": "error",
|
|
3211
3860
|
"typescript/restrict-template-expressions": "error",
|
|
3212
3861
|
"typescript/strict-boolean-expressions": ["error", {
|
|
3862
|
+
allowNullableObject: false,
|
|
3213
3863
|
allowNumber: false,
|
|
3214
3864
|
allowString: false
|
|
3215
3865
|
}],
|
|
@@ -3239,8 +3889,8 @@ const oxlint = defineConfig({
|
|
|
3239
3889
|
"no-restricted-imports": ["error", {
|
|
3240
3890
|
paths: [
|
|
3241
3891
|
{
|
|
3242
|
-
importNames:
|
|
3243
|
-
message:
|
|
3892
|
+
importNames: REACT_LEGACY_APIS,
|
|
3893
|
+
message: REACT_LEGACY_MESSAGE,
|
|
3244
3894
|
name: "react"
|
|
3245
3895
|
},
|
|
3246
3896
|
{
|
|
@@ -3303,6 +3953,7 @@ const oxlint = defineConfig({
|
|
|
3303
3953
|
}
|
|
3304
3954
|
]
|
|
3305
3955
|
}],
|
|
3956
|
+
"array-callback-return": "error",
|
|
3306
3957
|
"max-depth": ["error", 3],
|
|
3307
3958
|
"max-nested-callbacks": ["error", 6],
|
|
3308
3959
|
"no-cond-assign": "error",
|
|
@@ -3348,7 +3999,7 @@ const oxlint = defineConfig({
|
|
|
3348
3999
|
name: "Error"
|
|
3349
4000
|
},
|
|
3350
4001
|
{
|
|
3351
|
-
message:
|
|
4002
|
+
message: MAP_MESSAGE,
|
|
3352
4003
|
name: "Map"
|
|
3353
4004
|
},
|
|
3354
4005
|
{
|
|
@@ -3376,7 +4027,7 @@ const oxlint = defineConfig({
|
|
|
3376
4027
|
name: "Reflect"
|
|
3377
4028
|
},
|
|
3378
4029
|
{
|
|
3379
|
-
message:
|
|
4030
|
+
message: SET_MESSAGE,
|
|
3380
4031
|
name: "Set"
|
|
3381
4032
|
},
|
|
3382
4033
|
{
|
|
@@ -3403,13 +4054,13 @@ const oxlint = defineConfig({
|
|
|
3403
4054
|
message: "Use Predicate.hasProperty.",
|
|
3404
4055
|
property: "hasOwnProperty"
|
|
3405
4056
|
},
|
|
3406
|
-
...map$1(
|
|
3407
|
-
message:
|
|
4057
|
+
...map$1(RUNNERS, (property) => ({
|
|
4058
|
+
message: RUN_MESSAGE,
|
|
3408
4059
|
object: "Effect",
|
|
3409
4060
|
property
|
|
3410
4061
|
})),
|
|
3411
4062
|
{
|
|
3412
|
-
message:
|
|
4063
|
+
message: RUN_MESSAGE,
|
|
3413
4064
|
object: "ManagedRuntime",
|
|
3414
4065
|
property: "make"
|
|
3415
4066
|
},
|
|
@@ -3433,12 +4084,12 @@ const oxlint = defineConfig({
|
|
|
3433
4084
|
object: "Math",
|
|
3434
4085
|
property: "round"
|
|
3435
4086
|
},
|
|
3436
|
-
...map$1(
|
|
3437
|
-
message:
|
|
4087
|
+
...map$1(REACT_LEGACY_APIS, (property) => ({
|
|
4088
|
+
message: REACT_LEGACY_MESSAGE,
|
|
3438
4089
|
object: "React",
|
|
3439
4090
|
property
|
|
3440
4091
|
})),
|
|
3441
|
-
...map$1(
|
|
4092
|
+
...map$1(REACT_HOOKS, (property) => ({
|
|
3442
4093
|
message: `Import ${property} by name from react.`,
|
|
3443
4094
|
object: "React",
|
|
3444
4095
|
property
|
|
@@ -3555,6 +4206,12 @@ const oxlint = defineConfig({
|
|
|
3555
4206
|
ignoreRestSiblings: true,
|
|
3556
4207
|
reportUsedIgnorePattern: true
|
|
3557
4208
|
}],
|
|
4209
|
+
"no-use-before-define": ["error", {
|
|
4210
|
+
classes: false,
|
|
4211
|
+
functions: false,
|
|
4212
|
+
typedefs: false,
|
|
4213
|
+
variables: true
|
|
4214
|
+
}],
|
|
3558
4215
|
"no-useless-assignment": "error",
|
|
3559
4216
|
"no-useless-backreference": "error",
|
|
3560
4217
|
"no-useless-call": "error",
|
|
@@ -3576,6 +4233,12 @@ const oxlint = defineConfig({
|
|
|
3576
4233
|
"prefer-const": ["error", { destructuring: "all" }],
|
|
3577
4234
|
"prefer-template": "error",
|
|
3578
4235
|
"require-unicode-regexp": "error",
|
|
4236
|
+
"jsx-a11y/autocomplete-valid": "error",
|
|
4237
|
+
"jsx-a11y/click-events-have-key-events": "error",
|
|
4238
|
+
"jsx-a11y/control-has-associated-label": "error",
|
|
4239
|
+
"jsx-a11y/interactive-supports-focus": "error",
|
|
4240
|
+
"jsx-a11y/label-has-associated-control": "error",
|
|
4241
|
+
"react-doctor/no-aria-invalid-without-description": "error",
|
|
3579
4242
|
"react-doctor/no-call-component-as-function": "error",
|
|
3580
4243
|
"react-doctor/no-controlled-input-value-without-state-update": "error",
|
|
3581
4244
|
"react-doctor/no-create-context-in-render": "error",
|
|
@@ -3583,12 +4246,18 @@ const oxlint = defineConfig({
|
|
|
3583
4246
|
"react-doctor/no-inline-exhaustive-style": "error",
|
|
3584
4247
|
"react-doctor/no-jsx-element-type": "error",
|
|
3585
4248
|
"react-doctor/no-many-boolean-props": "error",
|
|
4249
|
+
"react-doctor/no-nested-card-surface": "error",
|
|
4250
|
+
"react-doctor/no-placeholder-only-field": "error",
|
|
3586
4251
|
"react-doctor/no-prop-types": "error",
|
|
3587
4252
|
"react-doctor/no-uncontrolled-input": "error",
|
|
4253
|
+
"react-doctor/shadcn-dialog-content-requires-title": "error",
|
|
3588
4254
|
"react-doctor/shadcn-icon-button-requires-label": "error",
|
|
3589
4255
|
"react-doctor/effect-listener-cleanup-mismatch": "error",
|
|
4256
|
+
"react-doctor/effect-needs-cleanup": "error",
|
|
4257
|
+
"react-doctor/no-adjust-state-on-prop-change": "error",
|
|
3590
4258
|
"react-doctor/no-async-effect-callback": "error",
|
|
3591
4259
|
"react-doctor/no-create-object-url-without-revoke": "error",
|
|
4260
|
+
"react-doctor/no-derived-state": "error",
|
|
3592
4261
|
"react-doctor/no-derived-useState": "error",
|
|
3593
4262
|
"react-doctor/no-effect-event-in-deps": "error",
|
|
3594
4263
|
"react-doctor/no-effect-with-fresh-deps": "error",
|
|
@@ -3616,10 +4285,10 @@ const oxlint = defineConfig({
|
|
|
3616
4285
|
"react-doctor/no-large-animated-blur": "error",
|
|
3617
4286
|
"react-doctor/no-layout-property-animation": "error",
|
|
3618
4287
|
"react-doctor/no-layout-transition-inline": "error",
|
|
3619
|
-
"react-doctor/no-long-transition-duration": "error",
|
|
3620
4288
|
"react-doctor/no-outline-none": "error",
|
|
3621
|
-
"react-doctor/no-
|
|
4289
|
+
"react-doctor/no-reduced-motion-content-removal": "error",
|
|
3622
4290
|
"react-doctor/no-transition-all": "error",
|
|
4291
|
+
"react-doctor/no-ungated-tailwind-animation": "error",
|
|
3623
4292
|
"react/button-has-type": "error",
|
|
3624
4293
|
"react/checked-requires-onchange-or-readonly": "error",
|
|
3625
4294
|
"react/exhaustive-deps": "error",
|
|
@@ -3649,6 +4318,7 @@ const oxlint = defineConfig({
|
|
|
3649
4318
|
"react/preserve-manual-memoization": "error",
|
|
3650
4319
|
"react/purity": "error",
|
|
3651
4320
|
"react/refs": "error",
|
|
4321
|
+
"react/rule-suppression": "error",
|
|
3652
4322
|
"react/rules-of-hooks": "error",
|
|
3653
4323
|
"react/self-closing-comp": "error",
|
|
3654
4324
|
"react/set-state-in-effect": "error",
|
|
@@ -3678,17 +4348,19 @@ const oxlint = defineConfig({
|
|
|
3678
4348
|
Date: "Use DateTime.",
|
|
3679
4349
|
Error: "Use Schema.TaggedError.",
|
|
3680
4350
|
Iterable: "Use T[].",
|
|
3681
|
-
Map:
|
|
4351
|
+
Map: MAP_MESSAGE,
|
|
3682
4352
|
Promise: "Use Effect.",
|
|
3683
4353
|
ReadonlyArray: "Use T[].",
|
|
3684
|
-
ReadonlyMap:
|
|
3685
|
-
ReadonlySet:
|
|
3686
|
-
Set:
|
|
4354
|
+
ReadonlyMap: MAP_MESSAGE,
|
|
4355
|
+
ReadonlySet: SET_MESSAGE,
|
|
4356
|
+
Set: SET_MESSAGE,
|
|
3687
4357
|
undefined: "Use an optional property or parameter, or let inference supply undefined."
|
|
3688
4358
|
} }],
|
|
3689
4359
|
"typescript/no-unnecessary-type-assertion": "error",
|
|
3690
4360
|
"typescript/no-useless-default-assignment": "error",
|
|
3691
4361
|
"unicorn/filename-case": "error",
|
|
4362
|
+
"unicorn/no-abusive-eslint-disable": "error",
|
|
4363
|
+
"unicorn/no-empty-file": "error",
|
|
3692
4364
|
"unicorn/no-immediate-mutation": "error",
|
|
3693
4365
|
"unicorn/no-null": "error",
|
|
3694
4366
|
"unicorn/no-object-as-default-parameter": "error",
|
|
@@ -3702,6 +4374,12 @@ const oxlint = defineConfig({
|
|
|
3702
4374
|
"use-isnan": "error"
|
|
3703
4375
|
},
|
|
3704
4376
|
settings: {
|
|
4377
|
+
"jsx-a11y": { components: {
|
|
4378
|
+
Button: "button",
|
|
4379
|
+
Input: "input",
|
|
4380
|
+
Label: "label",
|
|
4381
|
+
Textarea: "textarea"
|
|
4382
|
+
} },
|
|
3705
4383
|
react: { version: "19.0" },
|
|
3706
4384
|
shadcn: { ui: ["@deslop/components-web/ui", "@dual/ui/components"] }
|
|
3707
4385
|
}
|
|
@@ -3709,15 +4387,24 @@ const oxlint = defineConfig({
|
|
|
3709
4387
|
var oxlint_default = definePlugin({
|
|
3710
4388
|
meta: { name: "@deslop/coding-standards" },
|
|
3711
4389
|
rules: {
|
|
4390
|
+
"explicit-effect-concurrency": explicitEffectConcurrency,
|
|
4391
|
+
"module-constant": moduleConstant,
|
|
4392
|
+
"motion-limits": motionLimits,
|
|
3712
4393
|
"no-array-wrap-ternary": noArrayWrapTernary,
|
|
4394
|
+
"no-barrel-module": noBarrelModule,
|
|
4395
|
+
"no-compiler-opt-out": noCompilerOptOut,
|
|
4396
|
+
"no-component-atom-allocation": noComponentAtomAllocation,
|
|
3713
4397
|
"no-constant-function": noConstantFunction,
|
|
3714
4398
|
"no-deep-pipe": noDeepPipe,
|
|
3715
4399
|
"no-destructured-parameter": noDestructuredParameter,
|
|
3716
4400
|
"no-discarded-failure": noDiscardedFailure,
|
|
3717
4401
|
"no-double-nullish-check": noDoubleNullishCheck,
|
|
3718
4402
|
"no-effect-property-arrow": noEffectPropertyArrow,
|
|
4403
|
+
"no-empty-recovery": noEmptyRecovery,
|
|
3719
4404
|
"no-error-message-assertion": noErrorMessageAssertion,
|
|
3720
4405
|
"no-fake-ref-state": noFakeRefState,
|
|
4406
|
+
"no-floating-derived-type": noFloatingDerivedType,
|
|
4407
|
+
"no-gradient": noGradient,
|
|
3721
4408
|
"no-hand-written-guard": noHandWrittenGuard,
|
|
3722
4409
|
"no-in-operator": noInOperator,
|
|
3723
4410
|
"no-let": noLet,
|
|
@@ -3736,13 +4423,16 @@ var oxlint_default = definePlugin({
|
|
|
3736
4423
|
"no-renamed-import": noRenamedImport,
|
|
3737
4424
|
"no-return-type": noReturnType,
|
|
3738
4425
|
"no-single-use-value": noSingleUseValue,
|
|
4426
|
+
"no-stored-jsx": noStoredJsx,
|
|
3739
4427
|
"no-stored-schema-operation": noStoredSchemaOperation,
|
|
3740
4428
|
"no-switch-statement": noSwitchStatement,
|
|
3741
4429
|
"no-trivial-indirection": noTrivialIndirection,
|
|
4430
|
+
"no-type-from-value": noTypeFromValue,
|
|
3742
4431
|
"no-typeof": noTypeof,
|
|
3743
4432
|
"no-undestructured-use-state": noUndestructuredUseState,
|
|
3744
4433
|
"no-unexplained-disable": noUnexplainedDisable,
|
|
3745
4434
|
"no-unvalidated-json-decode": noUnvalidatedJsonDecode,
|
|
4435
|
+
"preserve-error-cause": preserveErrorCause,
|
|
3746
4436
|
"schema-type-pair": schemaTypePair
|
|
3747
4437
|
}
|
|
3748
4438
|
});
|