@timmo001/oxlint-rules 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -3
- package/dist/cli.js +436 -57
- package/dist/configs/recommended-effect.js +472 -97
- package/dist/configs/recommended.js +336 -16
- package/dist/effect/index.js +95 -11
- package/dist/generic/index.js +331 -15
- package/package.json +3 -1
- package/src/effect/index.ts +2 -0
- package/src/effect/rules/no-try-catch-in-effect-generators.ts +4 -98
- package/src/effect/rules/prefer-platform-services.ts +105 -0
- package/src/generic/index.ts +9 -0
- package/src/generic/rules/no-explicit-inferable-type-predicate.ts +146 -0
- package/src/generic/rules/no-unchecked-query-selector-type.ts +48 -0
- package/src/generic/rules/no-untyped-custom-event.ts +31 -0
- package/src/generic/rules/prefer-event-parameter-type.ts +36 -35
- package/src/generic/rules/prefer-lit-query-decorators.ts +84 -0
- package/src/generic/rules/prefer-types-over-assertions.ts +6 -5
- package/src/shared/ast.ts +31 -0
- package/src/shared/effect.ts +72 -0
- package/src/shared/json.ts +11 -0
- package/src/shared/lit.ts +219 -0
- package/src/shared/scope.ts +1 -0
|
@@ -2543,8 +2543,86 @@ var src_default = antiSlopPlugin;
|
|
|
2543
2543
|
// src/generic/index.ts
|
|
2544
2544
|
import { eslintCompatPlugin as eslintCompatPlugin2 } from "@oxlint/plugins";
|
|
2545
2545
|
|
|
2546
|
-
// src/generic/rules/
|
|
2546
|
+
// src/generic/rules/no-explicit-inferable-type-predicate.ts
|
|
2547
2547
|
import { defineRule as defineRule18 } from "@oxlint/plugins";
|
|
2548
|
+
function isNamed(node, name) {
|
|
2549
|
+
return node.type === "Identifier" && node.name === name;
|
|
2550
|
+
}
|
|
2551
|
+
function isNullish(node) {
|
|
2552
|
+
return node.type === "Literal" && node.value === null || isNamed(node, "undefined");
|
|
2553
|
+
}
|
|
2554
|
+
function isNullishExclusion(node, name) {
|
|
2555
|
+
return node.type === "BinaryExpression" && (node.operator === "!==" || node.operator === "!=") && (isNamed(node.left, name) && isNullish(node.right) || isNullish(node.left) && isNamed(node.right, name));
|
|
2556
|
+
}
|
|
2557
|
+
function isTypeofCheck(node, name) {
|
|
2558
|
+
if (node.type !== "BinaryExpression" || node.operator !== "===" && node.operator !== "==") {
|
|
2559
|
+
return false;
|
|
2560
|
+
}
|
|
2561
|
+
const isTypeof = (side) => side.type === "UnaryExpression" && side.operator === "typeof" && isNamed(side.argument, name);
|
|
2562
|
+
const isLiteral = (side) => side.type === "Literal";
|
|
2563
|
+
return isTypeof(node.left) && isLiteral(node.right) || isLiteral(node.left) && isTypeof(node.right);
|
|
2564
|
+
}
|
|
2565
|
+
function isInferableCheck(node, name) {
|
|
2566
|
+
if (node.type === "LogicalExpression") {
|
|
2567
|
+
return node.operator === "&&" && isNullishExclusion(node.left, name) && isNullishExclusion(node.right, name);
|
|
2568
|
+
}
|
|
2569
|
+
return isNullishExclusion(node, name) || isTypeofCheck(node, name) || node.type === "BinaryExpression" && node.operator === "instanceof" && isNamed(node.left, name);
|
|
2570
|
+
}
|
|
2571
|
+
function returnedExpression(owner) {
|
|
2572
|
+
const body = owner.body;
|
|
2573
|
+
if (!body)
|
|
2574
|
+
return null;
|
|
2575
|
+
if (body.type !== "BlockStatement")
|
|
2576
|
+
return body;
|
|
2577
|
+
const [statement] = body.body;
|
|
2578
|
+
return body.body.length === 1 && statement?.type === "ReturnStatement" ? statement.argument : null;
|
|
2579
|
+
}
|
|
2580
|
+
function isPublicSignature(owner) {
|
|
2581
|
+
const parent = owner.parent;
|
|
2582
|
+
if (parent.type === "MethodDefinition" || parent.type === "ExportNamedDeclaration" || parent.type === "ExportDefaultDeclaration") {
|
|
2583
|
+
return true;
|
|
2584
|
+
}
|
|
2585
|
+
return parent.type === "VariableDeclarator" && parent.parent.parent?.type === "ExportNamedDeclaration";
|
|
2586
|
+
}
|
|
2587
|
+
var noExplicitInferableTypePredicateRule = defineRule18({
|
|
2588
|
+
meta: {
|
|
2589
|
+
type: "suggestion",
|
|
2590
|
+
docs: {
|
|
2591
|
+
description: "Disallow explicit type predicates on simple null, typeof, or instanceof checks that TypeScript 5.5+ infers."
|
|
2592
|
+
},
|
|
2593
|
+
messages: {
|
|
2594
|
+
removeTypePredicate: "TypeScript infers the type predicate for `{{parameter}}` from this check, while an explicit predicate is never checked against the body. Remove the annotation and rely on the inferred predicate."
|
|
2595
|
+
}
|
|
2596
|
+
},
|
|
2597
|
+
create(context) {
|
|
2598
|
+
const check = (owner) => {
|
|
2599
|
+
const annotation = owner.returnType?.typeAnnotation;
|
|
2600
|
+
if (annotation?.type !== "TSTypePredicate" || annotation.asserts || annotation.parameterName.type !== "Identifier" || isPublicSignature(owner)) {
|
|
2601
|
+
return;
|
|
2602
|
+
}
|
|
2603
|
+
const parameter = annotation.parameterName.name;
|
|
2604
|
+
const expression = returnedExpression(owner);
|
|
2605
|
+
if (!owner.params.some((param) => isNamed(param, parameter)) || !expression || !isInferableCheck(expression, parameter)) {
|
|
2606
|
+
return;
|
|
2607
|
+
}
|
|
2608
|
+
context.report({
|
|
2609
|
+
node: annotation,
|
|
2610
|
+
messageId: "removeTypePredicate",
|
|
2611
|
+
data: { parameter }
|
|
2612
|
+
});
|
|
2613
|
+
};
|
|
2614
|
+
return {
|
|
2615
|
+
ArrowFunctionExpression: check,
|
|
2616
|
+
FunctionDeclaration: check,
|
|
2617
|
+
FunctionExpression: check
|
|
2618
|
+
};
|
|
2619
|
+
}
|
|
2620
|
+
});
|
|
2621
|
+
|
|
2622
|
+
// src/generic/rules/no-unchecked-query-selector-type.ts
|
|
2623
|
+
import { defineRule as defineRule19 } from "@oxlint/plugins";
|
|
2624
|
+
|
|
2625
|
+
// src/shared/ast.ts
|
|
2548
2626
|
function nearestEnclosingFunction(node) {
|
|
2549
2627
|
let current = node.parent;
|
|
2550
2628
|
while (current) {
|
|
@@ -2555,30 +2633,204 @@ function nearestEnclosingFunction(node) {
|
|
|
2555
2633
|
}
|
|
2556
2634
|
return null;
|
|
2557
2635
|
}
|
|
2558
|
-
function
|
|
2559
|
-
|
|
2560
|
-
if (expression.type !== "MemberExpression" || expression.computed || expression.object.type !== "Identifier" || expression.property.type !== "Identifier" || expression.property.name !== "currentTarget" && expression.property.name !== "target") {
|
|
2636
|
+
function staticMemberName(node) {
|
|
2637
|
+
if (node.type !== "MemberExpression" || node.computed)
|
|
2561
2638
|
return null;
|
|
2639
|
+
return node.property.type === "Identifier" ? node.property.name : null;
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
// src/shared/json.ts
|
|
2643
|
+
function jsonObject(value) {
|
|
2644
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
2645
|
+
}
|
|
2646
|
+
|
|
2647
|
+
// src/shared/lit.ts
|
|
2648
|
+
var renderRoots = new Set(["renderRoot", "shadowRoot"]);
|
|
2649
|
+
function litElementBaseClasses(settings) {
|
|
2650
|
+
const configured = jsonObject(settings.lit)?.elementBaseClasses;
|
|
2651
|
+
return new Set([
|
|
2652
|
+
"LitElement",
|
|
2653
|
+
...Array.isArray(configured) ? configured.map(String) : []
|
|
2654
|
+
]);
|
|
2655
|
+
}
|
|
2656
|
+
function isWrapperExpression(node) {
|
|
2657
|
+
return node?.type === "ChainExpression" || node?.type === "ParenthesizedExpression" || node?.type === "TSAsExpression" || node?.type === "TSNonNullExpression" || node?.type === "TSSatisfiesExpression" || node?.type === "TSTypeAssertion";
|
|
2658
|
+
}
|
|
2659
|
+
function unwrapExpression2(node) {
|
|
2660
|
+
let current = node;
|
|
2661
|
+
while (isWrapperExpression(current))
|
|
2662
|
+
current = current.expression;
|
|
2663
|
+
return current;
|
|
2664
|
+
}
|
|
2665
|
+
function extendsLitBase(node, bases) {
|
|
2666
|
+
if (node.type === "Identifier")
|
|
2667
|
+
return bases.has(node.name);
|
|
2668
|
+
return node.type === "CallExpression" && node.arguments.some((argument) => extendsLitBase(argument, bases));
|
|
2669
|
+
}
|
|
2670
|
+
function isLitClass(node, bases) {
|
|
2671
|
+
const hasCustomElementDecorator = node.decorators.some((decorator) => decorator.expression.type === "CallExpression" && decorator.expression.callee.type === "Identifier" && decorator.expression.callee.name === "customElement");
|
|
2672
|
+
return hasCustomElementDecorator || node.superClass !== null && extendsLitBase(node.superClass, bases);
|
|
2673
|
+
}
|
|
2674
|
+
function classOfBody(body) {
|
|
2675
|
+
const owner = body.parent;
|
|
2676
|
+
return owner?.type === "ClassDeclaration" || owner?.type === "ClassExpression" ? owner : null;
|
|
2677
|
+
}
|
|
2678
|
+
function thisClass(node) {
|
|
2679
|
+
let current = node;
|
|
2680
|
+
while (current.parent) {
|
|
2681
|
+
const parent = current.parent;
|
|
2682
|
+
if (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression") {
|
|
2683
|
+
const owner = parent.parent;
|
|
2684
|
+
return (owner?.type === "MethodDefinition" || owner?.type === "TSAbstractMethodDefinition") && !owner.static && owner.value === parent ? classOfBody(owner.parent) : null;
|
|
2685
|
+
}
|
|
2686
|
+
if ((parent.type === "PropertyDefinition" || parent.type === "AccessorProperty") && parent.value === current) {
|
|
2687
|
+
return parent.static ? null : classOfBody(parent.parent);
|
|
2688
|
+
}
|
|
2689
|
+
if (parent.type === "StaticBlock" || parent.type === "ClassBody" || parent.type === "Program") {
|
|
2690
|
+
return null;
|
|
2691
|
+
}
|
|
2692
|
+
current = parent;
|
|
2693
|
+
}
|
|
2694
|
+
return null;
|
|
2695
|
+
}
|
|
2696
|
+
function isStaticSelector(node) {
|
|
2697
|
+
return node?.type === "Literal" || node?.type === "TemplateLiteral" && node.expressions.length === 0;
|
|
2698
|
+
}
|
|
2699
|
+
function renderRootQuery(node, bases) {
|
|
2700
|
+
const method = staticMemberName(node.callee);
|
|
2701
|
+
if (method !== "querySelector" && method !== "querySelectorAll" || node.callee.type !== "MemberExpression" || !isStaticSelector(node.arguments[0])) {
|
|
2702
|
+
return null;
|
|
2703
|
+
}
|
|
2704
|
+
const receiver = unwrapExpression2(node.callee.object);
|
|
2705
|
+
const root = staticMemberName(receiver);
|
|
2706
|
+
if (!root || !renderRoots.has(root) || receiver.type !== "MemberExpression" || receiver.object.type !== "ThisExpression") {
|
|
2707
|
+
return null;
|
|
2708
|
+
}
|
|
2709
|
+
const owner = thisClass(receiver.object);
|
|
2710
|
+
return owner && isLitClass(owner, bases) ? { method, root } : null;
|
|
2711
|
+
}
|
|
2712
|
+
function isAssignedMethod(name) {
|
|
2713
|
+
return name === "assignedElements" || name === "assignedNodes";
|
|
2714
|
+
}
|
|
2715
|
+
function feedsAssignedCall(node) {
|
|
2716
|
+
let current = node;
|
|
2717
|
+
while (isWrapperExpression(current.parent))
|
|
2718
|
+
current = current.parent;
|
|
2719
|
+
const member = current.parent;
|
|
2720
|
+
return member?.type === "MemberExpression" && member.object === current && isAssignedMethod(staticMemberName(member));
|
|
2721
|
+
}
|
|
2722
|
+
function litQueryDecoratorCall(node, bases) {
|
|
2723
|
+
const method = staticMemberName(node.callee);
|
|
2724
|
+
if (!isAssignedMethod(method)) {
|
|
2725
|
+
return feedsAssignedCall(node) ? null : renderRootQuery(node, bases);
|
|
2726
|
+
}
|
|
2727
|
+
if (node.callee.type !== "MemberExpression")
|
|
2728
|
+
return null;
|
|
2729
|
+
const inner = unwrapExpression2(node.callee.object);
|
|
2730
|
+
if (inner.type !== "CallExpression")
|
|
2731
|
+
return null;
|
|
2732
|
+
const query = renderRootQuery(inner, bases);
|
|
2733
|
+
return query?.method === "querySelector" ? { method, root: query.root } : null;
|
|
2734
|
+
}
|
|
2735
|
+
function isLitRenderRootQuery(node, bases) {
|
|
2736
|
+
return renderRootQuery(node, bases) !== null;
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
// src/generic/rules/no-unchecked-query-selector-type.ts
|
|
2740
|
+
var queryMethods = new Set(["closest", "querySelector", "querySelectorAll"]);
|
|
2741
|
+
var noUncheckedQuerySelectorTypeRule = defineRule19({
|
|
2742
|
+
meta: {
|
|
2743
|
+
type: "problem",
|
|
2744
|
+
docs: {
|
|
2745
|
+
description: "Disallow type arguments on `querySelector`, `querySelectorAll`, and `closest`, which cast the result without checking it."
|
|
2746
|
+
},
|
|
2747
|
+
messages: {
|
|
2748
|
+
checkQueryResult: "`{{method}}<T>` casts the result without checking it. Check it with `instanceof`, use a checked lookup helper, or query a tag name so TypeScript infers the element type."
|
|
2749
|
+
}
|
|
2750
|
+
},
|
|
2751
|
+
create(context) {
|
|
2752
|
+
const bases = litElementBaseClasses(context.settings);
|
|
2753
|
+
return {
|
|
2754
|
+
CallExpression(node) {
|
|
2755
|
+
const method = staticMemberName(node.callee);
|
|
2756
|
+
if (!node.typeArguments || !method || !queryMethods.has(method) || isLitRenderRootQuery(node, bases)) {
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2759
|
+
context.report({
|
|
2760
|
+
node: node.typeArguments,
|
|
2761
|
+
messageId: "checkQueryResult",
|
|
2762
|
+
data: { method }
|
|
2763
|
+
});
|
|
2764
|
+
}
|
|
2765
|
+
};
|
|
2766
|
+
}
|
|
2767
|
+
});
|
|
2768
|
+
|
|
2769
|
+
// src/generic/rules/no-untyped-custom-event.ts
|
|
2770
|
+
import { defineRule as defineRule20 } from "@oxlint/plugins";
|
|
2771
|
+
var noUntypedCustomEventRule = defineRule20({
|
|
2772
|
+
meta: {
|
|
2773
|
+
type: "problem",
|
|
2774
|
+
docs: {
|
|
2775
|
+
description: "Disallow `CustomEvent` type references without a detail type, which leave `detail` as `any`."
|
|
2776
|
+
},
|
|
2777
|
+
messages: {
|
|
2778
|
+
typeCustomEventDetail: "`CustomEvent` without a type argument leaves `detail` as `any`. Reuse the project's typed event helper if one fits, or add one, instead of a bare `CustomEvent`."
|
|
2779
|
+
}
|
|
2780
|
+
},
|
|
2781
|
+
create(context) {
|
|
2782
|
+
return {
|
|
2783
|
+
TSTypeReference(node) {
|
|
2784
|
+
if (node.typeName.type !== "Identifier" || node.typeName.name !== "CustomEvent" || node.typeArguments) {
|
|
2785
|
+
return;
|
|
2786
|
+
}
|
|
2787
|
+
context.report({ node, messageId: "typeCustomEventDetail" });
|
|
2788
|
+
}
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
});
|
|
2792
|
+
|
|
2793
|
+
// src/generic/rules/prefer-event-parameter-type.ts
|
|
2794
|
+
import { defineRule as defineRule21 } from "@oxlint/plugins";
|
|
2795
|
+
function staticMemberPath(node) {
|
|
2796
|
+
const path = [];
|
|
2797
|
+
let current = node.type === "ChainExpression" ? node.expression : node;
|
|
2798
|
+
while (current.type === "MemberExpression") {
|
|
2799
|
+
if (current.computed || current.property.type !== "Identifier") {
|
|
2800
|
+
return null;
|
|
2801
|
+
}
|
|
2802
|
+
path.unshift(current.property.name);
|
|
2803
|
+
current = current.object;
|
|
2562
2804
|
}
|
|
2563
|
-
|
|
2564
|
-
|
|
2805
|
+
return current.type === "Identifier" && path.length > 0 ? { root: current.name, path } : null;
|
|
2806
|
+
}
|
|
2807
|
+
function isEventMemberPath(path) {
|
|
2808
|
+
const [first] = path;
|
|
2809
|
+
if (first === "detail")
|
|
2810
|
+
return true;
|
|
2811
|
+
return path.length === 1 && (first === "currentTarget" || first === "target");
|
|
2812
|
+
}
|
|
2813
|
+
function assertedEventParameter(node) {
|
|
2814
|
+
const member = staticMemberPath(node.expression);
|
|
2815
|
+
if (!member || !isEventMemberPath(member.path))
|
|
2816
|
+
return null;
|
|
2565
2817
|
const owner = nearestEnclosingFunction(node);
|
|
2566
|
-
if (!owner?.params.some((parameter) => parameter.type === "Identifier" && parameter.name ===
|
|
2818
|
+
if (!owner?.params.some((parameter) => parameter.type === "Identifier" && parameter.name === member.root)) {
|
|
2567
2819
|
return null;
|
|
2568
2820
|
}
|
|
2569
2821
|
return {
|
|
2570
|
-
parameter:
|
|
2571
|
-
property
|
|
2822
|
+
parameter: member.root,
|
|
2823
|
+
property: member.path.join(".")
|
|
2572
2824
|
};
|
|
2573
2825
|
}
|
|
2574
|
-
var preferEventParameterTypeRule =
|
|
2826
|
+
var preferEventParameterTypeRule = defineRule21({
|
|
2575
2827
|
meta: {
|
|
2576
2828
|
type: "suggestion",
|
|
2577
2829
|
docs: {
|
|
2578
|
-
description: "Prefer typing event target properties in the handler parameter instead of asserting them at use sites."
|
|
2830
|
+
description: "Prefer typing event target and detail properties in the handler parameter instead of asserting them at use sites."
|
|
2579
2831
|
},
|
|
2580
2832
|
messages: {
|
|
2581
|
-
typeEventParameter: "Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site."
|
|
2833
|
+
typeEventParameter: "Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site. Reuse the project's typed event helper if one fits, or add one."
|
|
2582
2834
|
}
|
|
2583
2835
|
},
|
|
2584
2836
|
create(context) {
|
|
@@ -2599,9 +2851,69 @@ var preferEventParameterTypeRule = defineRule18({
|
|
|
2599
2851
|
}
|
|
2600
2852
|
});
|
|
2601
2853
|
|
|
2854
|
+
// src/generic/rules/prefer-lit-query-decorators.ts
|
|
2855
|
+
import { defineRule as defineRule22 } from "@oxlint/plugins";
|
|
2856
|
+
var messageIds = {
|
|
2857
|
+
assignedElements: "preferQueryAssignedElements",
|
|
2858
|
+
assignedNodes: "preferQueryAssignedNodes",
|
|
2859
|
+
querySelector: "preferQuery",
|
|
2860
|
+
querySelectorAll: "preferQueryAll"
|
|
2861
|
+
};
|
|
2862
|
+
var preferLitQueryDecoratorsRule = defineRule22({
|
|
2863
|
+
meta: {
|
|
2864
|
+
type: "suggestion",
|
|
2865
|
+
docs: {
|
|
2866
|
+
description: "Require Lit query decorators instead of static render-root queries in Lit elements."
|
|
2867
|
+
},
|
|
2868
|
+
schema: [
|
|
2869
|
+
{
|
|
2870
|
+
type: "object",
|
|
2871
|
+
properties: {
|
|
2872
|
+
querySelector: { type: "boolean" },
|
|
2873
|
+
querySelectorAll: { type: "boolean" },
|
|
2874
|
+
assignedElements: { type: "boolean" },
|
|
2875
|
+
assignedNodes: { type: "boolean" }
|
|
2876
|
+
},
|
|
2877
|
+
additionalProperties: false
|
|
2878
|
+
}
|
|
2879
|
+
],
|
|
2880
|
+
defaultOptions: [
|
|
2881
|
+
{
|
|
2882
|
+
querySelector: true,
|
|
2883
|
+
querySelectorAll: true,
|
|
2884
|
+
assignedElements: true,
|
|
2885
|
+
assignedNodes: true
|
|
2886
|
+
}
|
|
2887
|
+
],
|
|
2888
|
+
messages: {
|
|
2889
|
+
preferQuery: "Use the @query decorator instead of this.{{root}}.querySelector().",
|
|
2890
|
+
preferQueryAll: "Use the @queryAll decorator instead of this.{{root}}.querySelectorAll().",
|
|
2891
|
+
preferQueryAssignedElements: "Use the @queryAssignedElements decorator instead of this.{{root}}.querySelector().assignedElements().",
|
|
2892
|
+
preferQueryAssignedNodes: "Use the @queryAssignedNodes decorator instead of this.{{root}}.querySelector().assignedNodes()."
|
|
2893
|
+
}
|
|
2894
|
+
},
|
|
2895
|
+
create(context) {
|
|
2896
|
+
const bases = litElementBaseClasses(context.settings);
|
|
2897
|
+
const options = jsonObject(context.options[0]);
|
|
2898
|
+
const disabled = new Set(options ? Object.keys(options).filter((method) => options[method] === false) : []);
|
|
2899
|
+
return {
|
|
2900
|
+
CallExpression(node) {
|
|
2901
|
+
const query = litQueryDecoratorCall(node, bases);
|
|
2902
|
+
if (!query || disabled.has(query.method))
|
|
2903
|
+
return;
|
|
2904
|
+
context.report({
|
|
2905
|
+
node,
|
|
2906
|
+
messageId: messageIds[query.method],
|
|
2907
|
+
data: { root: query.root }
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2911
|
+
}
|
|
2912
|
+
});
|
|
2913
|
+
|
|
2602
2914
|
// src/generic/rules/prefer-types-over-assertions.ts
|
|
2603
|
-
import { defineRule as
|
|
2604
|
-
var preferTypesOverAssertionsRule =
|
|
2915
|
+
import { defineRule as defineRule23 } from "@oxlint/plugins";
|
|
2916
|
+
var preferTypesOverAssertionsRule = defineRule23({
|
|
2605
2917
|
meta: {
|
|
2606
2918
|
type: "problem",
|
|
2607
2919
|
docs: {
|
|
@@ -2613,7 +2925,7 @@ var preferTypesOverAssertionsRule = defineRule19({
|
|
|
2613
2925
|
},
|
|
2614
2926
|
create(context) {
|
|
2615
2927
|
const checkAssertion = (node) => {
|
|
2616
|
-
if (node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const") {
|
|
2928
|
+
if (node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const" || assertedEventParameter(node)) {
|
|
2617
2929
|
return;
|
|
2618
2930
|
}
|
|
2619
2931
|
context.report({ node, messageId: "avoidAssertion" });
|
|
@@ -2629,7 +2941,11 @@ var preferTypesOverAssertionsRule = defineRule19({
|
|
|
2629
2941
|
var timmoPlugin = eslintCompatPlugin2({
|
|
2630
2942
|
meta: { name: "timmo" },
|
|
2631
2943
|
rules: {
|
|
2944
|
+
"no-explicit-inferable-type-predicate": noExplicitInferableTypePredicateRule,
|
|
2945
|
+
"no-unchecked-query-selector-type": noUncheckedQuerySelectorTypeRule,
|
|
2946
|
+
"no-untyped-custom-event": noUntypedCustomEventRule,
|
|
2632
2947
|
"prefer-event-parameter-type": preferEventParameterTypeRule,
|
|
2948
|
+
"prefer-lit-query-decorators": preferLitQueryDecoratorsRule,
|
|
2633
2949
|
"prefer-types-over-assertions": preferTypesOverAssertionsRule
|
|
2634
2950
|
}
|
|
2635
2951
|
});
|
|
@@ -2652,7 +2968,11 @@ var recommended = defineConfig({
|
|
|
2652
2968
|
rules: {
|
|
2653
2969
|
...enablePluginRules("anti-slop", src_default),
|
|
2654
2970
|
...enablePluginRules("timmo", generic_default),
|
|
2655
|
-
"anti-slop/require-safety-comment-for-type-assertion": "off"
|
|
2971
|
+
"anti-slop/require-safety-comment-for-type-assertion": "off",
|
|
2972
|
+
"typescript/no-non-null-assertion": "error",
|
|
2973
|
+
"typescript/no-unnecessary-type-arguments": "error",
|
|
2974
|
+
"typescript/no-unnecessary-type-assertion": "error",
|
|
2975
|
+
"typescript/no-unnecessary-type-parameters": "error"
|
|
2656
2976
|
}
|
|
2657
2977
|
});
|
|
2658
2978
|
var recommended_default = recommended;
|
package/dist/effect/index.js
CHANGED
|
@@ -3,16 +3,8 @@ import { eslintCompatPlugin } from "@oxlint/plugins";
|
|
|
3
3
|
|
|
4
4
|
// src/effect/rules/no-try-catch-in-effect-generators.ts
|
|
5
5
|
import { defineRule } from "@oxlint/plugins";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
while (scope) {
|
|
9
|
-
const variable = scope.set.get(identifier.name);
|
|
10
|
-
if (variable)
|
|
11
|
-
return variable;
|
|
12
|
-
scope = scope.upper;
|
|
13
|
-
}
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
6
|
+
|
|
7
|
+
// src/shared/ast.ts
|
|
16
8
|
function nearestEnclosingFunction(node) {
|
|
17
9
|
let current = node.parent;
|
|
18
10
|
while (current) {
|
|
@@ -28,6 +20,19 @@ function staticMemberName(node) {
|
|
|
28
20
|
return null;
|
|
29
21
|
return node.property.type === "Identifier" ? node.property.name : null;
|
|
30
22
|
}
|
|
23
|
+
|
|
24
|
+
// vendor/anti-slop/src/shared/scope.ts
|
|
25
|
+
function resolveVariable(sourceCode, identifier) {
|
|
26
|
+
let scope = sourceCode.getScope(identifier);
|
|
27
|
+
while (scope !== null) {
|
|
28
|
+
const variable = scope.set.get(identifier.name);
|
|
29
|
+
if (variable !== undefined)
|
|
30
|
+
return variable;
|
|
31
|
+
scope = scope.upper;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
// src/shared/effect.ts
|
|
31
36
|
function isEffectImport(sourceCode, identifier, namespace) {
|
|
32
37
|
const variable = resolveVariable(sourceCode, identifier);
|
|
33
38
|
return variable?.defs.some((definition) => {
|
|
@@ -55,6 +60,11 @@ function isEffectMethod(sourceCode, node, method) {
|
|
|
55
60
|
}
|
|
56
61
|
return isEffectImport(sourceCode, object.object, true);
|
|
57
62
|
}
|
|
63
|
+
function importsEffect(program) {
|
|
64
|
+
return program.body.some((statement) => statement.type === "ImportDeclaration" && statement.importKind !== "type" && (statement.source.value === "effect" || statement.source.value.startsWith("effect/") || statement.source.value.startsWith("@effect/")));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/effect/rules/no-try-catch-in-effect-generators.ts
|
|
58
68
|
function isDirectArgument(owner, call) {
|
|
59
69
|
return call.arguments.some((argument) => argument === owner);
|
|
60
70
|
}
|
|
@@ -93,11 +103,85 @@ var noTryCatchInEffectGeneratorsRule = defineRule({
|
|
|
93
103
|
}
|
|
94
104
|
});
|
|
95
105
|
|
|
106
|
+
// src/effect/rules/prefer-platform-services.ts
|
|
107
|
+
import { defineRule as defineRule2 } from "@oxlint/plugins";
|
|
108
|
+
var moduleServices = new Map([
|
|
109
|
+
["child_process", "childProcess"],
|
|
110
|
+
["fs", "fileSystem"],
|
|
111
|
+
["fs/promises", "fileSystem"]
|
|
112
|
+
]);
|
|
113
|
+
var bunServices = new Map([
|
|
114
|
+
["$", "childProcess"],
|
|
115
|
+
["file", "fileSystem"],
|
|
116
|
+
["spawn", "childProcess"],
|
|
117
|
+
["spawnSync", "childProcess"],
|
|
118
|
+
["write", "fileSystem"]
|
|
119
|
+
]);
|
|
120
|
+
var preferPlatformServicesRule = defineRule2({
|
|
121
|
+
meta: {
|
|
122
|
+
type: "suggestion",
|
|
123
|
+
docs: {
|
|
124
|
+
description: "Prefer Effect's ChildProcessSpawner and FileSystem services over Node or Bun process and file system APIs in modules that use Effect."
|
|
125
|
+
},
|
|
126
|
+
messages: {
|
|
127
|
+
childProcess: "Use Effect's `ChildProcessSpawner` service (`effect/process`) instead of `{{api}}`, so scope cleanup, interruption, and test layers apply.",
|
|
128
|
+
fileSystem: "Use Effect's `FileSystem` service instead of `{{api}}`, so failures stay typed and tests can provide a layer."
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
create(context) {
|
|
132
|
+
let enabled = false;
|
|
133
|
+
return {
|
|
134
|
+
Program(node) {
|
|
135
|
+
enabled = importsEffect(node);
|
|
136
|
+
},
|
|
137
|
+
ImportDeclaration(node) {
|
|
138
|
+
if (!enabled || node.importKind === "type")
|
|
139
|
+
return;
|
|
140
|
+
const source = node.source.value;
|
|
141
|
+
const service = moduleServices.get(source.replace(/^node:/u, ""));
|
|
142
|
+
if (service) {
|
|
143
|
+
context.report({ node, messageId: service, data: { api: source } });
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (source !== "bun")
|
|
147
|
+
return;
|
|
148
|
+
for (const specifier of node.specifiers) {
|
|
149
|
+
if (specifier.type !== "ImportSpecifier" || specifier.importKind === "type") {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const imported = specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
|
|
153
|
+
const bunService = bunServices.get(imported);
|
|
154
|
+
if (bunService) {
|
|
155
|
+
context.report({
|
|
156
|
+
node: specifier,
|
|
157
|
+
messageId: bunService,
|
|
158
|
+
data: { api: `Bun.${imported}` }
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
MemberExpression(node) {
|
|
164
|
+
const name = staticMemberName(node);
|
|
165
|
+
const service = name ? bunServices.get(name) : undefined;
|
|
166
|
+
if (!enabled || !service || node.object.type !== "Identifier" || node.object.name !== "Bun" || (resolveVariable(context.sourceCode, node.object)?.defs.length ?? 0) > 0) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
context.report({
|
|
170
|
+
node,
|
|
171
|
+
messageId: service,
|
|
172
|
+
data: { api: `Bun.${name}` }
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
96
179
|
// src/effect/index.ts
|
|
97
180
|
var timmoEffectPlugin = eslintCompatPlugin({
|
|
98
181
|
meta: { name: "timmo-effect" },
|
|
99
182
|
rules: {
|
|
100
|
-
"no-try-catch-in-effect-generators": noTryCatchInEffectGeneratorsRule
|
|
183
|
+
"no-try-catch-in-effect-generators": noTryCatchInEffectGeneratorsRule,
|
|
184
|
+
"prefer-platform-services": preferPlatformServicesRule
|
|
101
185
|
}
|
|
102
186
|
});
|
|
103
187
|
var effect_default = timmoEffectPlugin;
|