@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/dist/cli.js CHANGED
@@ -2548,8 +2548,86 @@ var src_default = antiSlopPlugin;
2548
2548
  // src/generic/index.ts
2549
2549
  import { eslintCompatPlugin as eslintCompatPlugin2 } from "@oxlint/plugins";
2550
2550
 
2551
- // src/generic/rules/prefer-event-parameter-type.ts
2551
+ // src/generic/rules/no-explicit-inferable-type-predicate.ts
2552
2552
  import { defineRule as defineRule18 } from "@oxlint/plugins";
2553
+ function isNamed(node, name) {
2554
+ return node.type === "Identifier" && node.name === name;
2555
+ }
2556
+ function isNullish(node) {
2557
+ return node.type === "Literal" && node.value === null || isNamed(node, "undefined");
2558
+ }
2559
+ function isNullishExclusion(node, name) {
2560
+ return node.type === "BinaryExpression" && (node.operator === "!==" || node.operator === "!=") && (isNamed(node.left, name) && isNullish(node.right) || isNullish(node.left) && isNamed(node.right, name));
2561
+ }
2562
+ function isTypeofCheck(node, name) {
2563
+ if (node.type !== "BinaryExpression" || node.operator !== "===" && node.operator !== "==") {
2564
+ return false;
2565
+ }
2566
+ const isTypeof = (side) => side.type === "UnaryExpression" && side.operator === "typeof" && isNamed(side.argument, name);
2567
+ const isLiteral = (side) => side.type === "Literal";
2568
+ return isTypeof(node.left) && isLiteral(node.right) || isLiteral(node.left) && isTypeof(node.right);
2569
+ }
2570
+ function isInferableCheck(node, name) {
2571
+ if (node.type === "LogicalExpression") {
2572
+ return node.operator === "&&" && isNullishExclusion(node.left, name) && isNullishExclusion(node.right, name);
2573
+ }
2574
+ return isNullishExclusion(node, name) || isTypeofCheck(node, name) || node.type === "BinaryExpression" && node.operator === "instanceof" && isNamed(node.left, name);
2575
+ }
2576
+ function returnedExpression(owner) {
2577
+ const body = owner.body;
2578
+ if (!body)
2579
+ return null;
2580
+ if (body.type !== "BlockStatement")
2581
+ return body;
2582
+ const [statement] = body.body;
2583
+ return body.body.length === 1 && statement?.type === "ReturnStatement" ? statement.argument : null;
2584
+ }
2585
+ function isPublicSignature(owner) {
2586
+ const parent = owner.parent;
2587
+ if (parent.type === "MethodDefinition" || parent.type === "ExportNamedDeclaration" || parent.type === "ExportDefaultDeclaration") {
2588
+ return true;
2589
+ }
2590
+ return parent.type === "VariableDeclarator" && parent.parent.parent?.type === "ExportNamedDeclaration";
2591
+ }
2592
+ var noExplicitInferableTypePredicateRule = defineRule18({
2593
+ meta: {
2594
+ type: "suggestion",
2595
+ docs: {
2596
+ description: "Disallow explicit type predicates on simple null, typeof, or instanceof checks that TypeScript 5.5+ infers."
2597
+ },
2598
+ messages: {
2599
+ 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."
2600
+ }
2601
+ },
2602
+ create(context) {
2603
+ const check = (owner) => {
2604
+ const annotation = owner.returnType?.typeAnnotation;
2605
+ if (annotation?.type !== "TSTypePredicate" || annotation.asserts || annotation.parameterName.type !== "Identifier" || isPublicSignature(owner)) {
2606
+ return;
2607
+ }
2608
+ const parameter = annotation.parameterName.name;
2609
+ const expression = returnedExpression(owner);
2610
+ if (!owner.params.some((param) => isNamed(param, parameter)) || !expression || !isInferableCheck(expression, parameter)) {
2611
+ return;
2612
+ }
2613
+ context.report({
2614
+ node: annotation,
2615
+ messageId: "removeTypePredicate",
2616
+ data: { parameter }
2617
+ });
2618
+ };
2619
+ return {
2620
+ ArrowFunctionExpression: check,
2621
+ FunctionDeclaration: check,
2622
+ FunctionExpression: check
2623
+ };
2624
+ }
2625
+ });
2626
+
2627
+ // src/generic/rules/no-unchecked-query-selector-type.ts
2628
+ import { defineRule as defineRule19 } from "@oxlint/plugins";
2629
+
2630
+ // src/shared/ast.ts
2553
2631
  function nearestEnclosingFunction(node) {
2554
2632
  let current = node.parent;
2555
2633
  while (current) {
@@ -2560,30 +2638,204 @@ function nearestEnclosingFunction(node) {
2560
2638
  }
2561
2639
  return null;
2562
2640
  }
2563
- function assertedEventParameter(node) {
2564
- const expression = node.expression;
2565
- if (expression.type !== "MemberExpression" || expression.computed || expression.object.type !== "Identifier" || expression.property.type !== "Identifier" || expression.property.name !== "currentTarget" && expression.property.name !== "target") {
2641
+ function staticMemberName(node) {
2642
+ if (node.type !== "MemberExpression" || node.computed)
2643
+ return null;
2644
+ return node.property.type === "Identifier" ? node.property.name : null;
2645
+ }
2646
+
2647
+ // src/shared/json.ts
2648
+ function jsonObject(value) {
2649
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
2650
+ }
2651
+
2652
+ // src/shared/lit.ts
2653
+ var renderRoots = new Set(["renderRoot", "shadowRoot"]);
2654
+ function litElementBaseClasses(settings) {
2655
+ const configured = jsonObject(settings.lit)?.elementBaseClasses;
2656
+ return new Set([
2657
+ "LitElement",
2658
+ ...Array.isArray(configured) ? configured.map(String) : []
2659
+ ]);
2660
+ }
2661
+ function isWrapperExpression(node) {
2662
+ return node?.type === "ChainExpression" || node?.type === "ParenthesizedExpression" || node?.type === "TSAsExpression" || node?.type === "TSNonNullExpression" || node?.type === "TSSatisfiesExpression" || node?.type === "TSTypeAssertion";
2663
+ }
2664
+ function unwrapExpression2(node) {
2665
+ let current = node;
2666
+ while (isWrapperExpression(current))
2667
+ current = current.expression;
2668
+ return current;
2669
+ }
2670
+ function extendsLitBase(node, bases) {
2671
+ if (node.type === "Identifier")
2672
+ return bases.has(node.name);
2673
+ return node.type === "CallExpression" && node.arguments.some((argument) => extendsLitBase(argument, bases));
2674
+ }
2675
+ function isLitClass(node, bases) {
2676
+ const hasCustomElementDecorator = node.decorators.some((decorator) => decorator.expression.type === "CallExpression" && decorator.expression.callee.type === "Identifier" && decorator.expression.callee.name === "customElement");
2677
+ return hasCustomElementDecorator || node.superClass !== null && extendsLitBase(node.superClass, bases);
2678
+ }
2679
+ function classOfBody(body) {
2680
+ const owner = body.parent;
2681
+ return owner?.type === "ClassDeclaration" || owner?.type === "ClassExpression" ? owner : null;
2682
+ }
2683
+ function thisClass(node) {
2684
+ let current = node;
2685
+ while (current.parent) {
2686
+ const parent = current.parent;
2687
+ if (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression") {
2688
+ const owner = parent.parent;
2689
+ return (owner?.type === "MethodDefinition" || owner?.type === "TSAbstractMethodDefinition") && !owner.static && owner.value === parent ? classOfBody(owner.parent) : null;
2690
+ }
2691
+ if ((parent.type === "PropertyDefinition" || parent.type === "AccessorProperty") && parent.value === current) {
2692
+ return parent.static ? null : classOfBody(parent.parent);
2693
+ }
2694
+ if (parent.type === "StaticBlock" || parent.type === "ClassBody" || parent.type === "Program") {
2695
+ return null;
2696
+ }
2697
+ current = parent;
2698
+ }
2699
+ return null;
2700
+ }
2701
+ function isStaticSelector(node) {
2702
+ return node?.type === "Literal" || node?.type === "TemplateLiteral" && node.expressions.length === 0;
2703
+ }
2704
+ function renderRootQuery(node, bases) {
2705
+ const method = staticMemberName(node.callee);
2706
+ if (method !== "querySelector" && method !== "querySelectorAll" || node.callee.type !== "MemberExpression" || !isStaticSelector(node.arguments[0])) {
2707
+ return null;
2708
+ }
2709
+ const receiver = unwrapExpression2(node.callee.object);
2710
+ const root = staticMemberName(receiver);
2711
+ if (!root || !renderRoots.has(root) || receiver.type !== "MemberExpression" || receiver.object.type !== "ThisExpression") {
2712
+ return null;
2713
+ }
2714
+ const owner = thisClass(receiver.object);
2715
+ return owner && isLitClass(owner, bases) ? { method, root } : null;
2716
+ }
2717
+ function isAssignedMethod(name) {
2718
+ return name === "assignedElements" || name === "assignedNodes";
2719
+ }
2720
+ function feedsAssignedCall(node) {
2721
+ let current = node;
2722
+ while (isWrapperExpression(current.parent))
2723
+ current = current.parent;
2724
+ const member = current.parent;
2725
+ return member?.type === "MemberExpression" && member.object === current && isAssignedMethod(staticMemberName(member));
2726
+ }
2727
+ function litQueryDecoratorCall(node, bases) {
2728
+ const method = staticMemberName(node.callee);
2729
+ if (!isAssignedMethod(method)) {
2730
+ return feedsAssignedCall(node) ? null : renderRootQuery(node, bases);
2731
+ }
2732
+ if (node.callee.type !== "MemberExpression")
2733
+ return null;
2734
+ const inner = unwrapExpression2(node.callee.object);
2735
+ if (inner.type !== "CallExpression")
2566
2736
  return null;
2737
+ const query = renderRootQuery(inner, bases);
2738
+ return query?.method === "querySelector" ? { method, root: query.root } : null;
2739
+ }
2740
+ function isLitRenderRootQuery(node, bases) {
2741
+ return renderRootQuery(node, bases) !== null;
2742
+ }
2743
+
2744
+ // src/generic/rules/no-unchecked-query-selector-type.ts
2745
+ var queryMethods = new Set(["closest", "querySelector", "querySelectorAll"]);
2746
+ var noUncheckedQuerySelectorTypeRule = defineRule19({
2747
+ meta: {
2748
+ type: "problem",
2749
+ docs: {
2750
+ description: "Disallow type arguments on `querySelector`, `querySelectorAll`, and `closest`, which cast the result without checking it."
2751
+ },
2752
+ messages: {
2753
+ 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."
2754
+ }
2755
+ },
2756
+ create(context) {
2757
+ const bases = litElementBaseClasses(context.settings);
2758
+ return {
2759
+ CallExpression(node) {
2760
+ const method = staticMemberName(node.callee);
2761
+ if (!node.typeArguments || !method || !queryMethods.has(method) || isLitRenderRootQuery(node, bases)) {
2762
+ return;
2763
+ }
2764
+ context.report({
2765
+ node: node.typeArguments,
2766
+ messageId: "checkQueryResult",
2767
+ data: { method }
2768
+ });
2769
+ }
2770
+ };
2771
+ }
2772
+ });
2773
+
2774
+ // src/generic/rules/no-untyped-custom-event.ts
2775
+ import { defineRule as defineRule20 } from "@oxlint/plugins";
2776
+ var noUntypedCustomEventRule = defineRule20({
2777
+ meta: {
2778
+ type: "problem",
2779
+ docs: {
2780
+ description: "Disallow `CustomEvent` type references without a detail type, which leave `detail` as `any`."
2781
+ },
2782
+ messages: {
2783
+ 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`."
2784
+ }
2785
+ },
2786
+ create(context) {
2787
+ return {
2788
+ TSTypeReference(node) {
2789
+ if (node.typeName.type !== "Identifier" || node.typeName.name !== "CustomEvent" || node.typeArguments) {
2790
+ return;
2791
+ }
2792
+ context.report({ node, messageId: "typeCustomEventDetail" });
2793
+ }
2794
+ };
2567
2795
  }
2568
- const parameterName = expression.object.name;
2569
- const property = expression.property.name;
2796
+ });
2797
+
2798
+ // src/generic/rules/prefer-event-parameter-type.ts
2799
+ import { defineRule as defineRule21 } from "@oxlint/plugins";
2800
+ function staticMemberPath(node) {
2801
+ const path = [];
2802
+ let current = node.type === "ChainExpression" ? node.expression : node;
2803
+ while (current.type === "MemberExpression") {
2804
+ if (current.computed || current.property.type !== "Identifier") {
2805
+ return null;
2806
+ }
2807
+ path.unshift(current.property.name);
2808
+ current = current.object;
2809
+ }
2810
+ return current.type === "Identifier" && path.length > 0 ? { root: current.name, path } : null;
2811
+ }
2812
+ function isEventMemberPath(path) {
2813
+ const [first] = path;
2814
+ if (first === "detail")
2815
+ return true;
2816
+ return path.length === 1 && (first === "currentTarget" || first === "target");
2817
+ }
2818
+ function assertedEventParameter(node) {
2819
+ const member = staticMemberPath(node.expression);
2820
+ if (!member || !isEventMemberPath(member.path))
2821
+ return null;
2570
2822
  const owner = nearestEnclosingFunction(node);
2571
- if (!owner?.params.some((parameter) => parameter.type === "Identifier" && parameter.name === parameterName)) {
2823
+ if (!owner?.params.some((parameter) => parameter.type === "Identifier" && parameter.name === member.root)) {
2572
2824
  return null;
2573
2825
  }
2574
2826
  return {
2575
- parameter: parameterName,
2576
- property
2827
+ parameter: member.root,
2828
+ property: member.path.join(".")
2577
2829
  };
2578
2830
  }
2579
- var preferEventParameterTypeRule = defineRule18({
2831
+ var preferEventParameterTypeRule = defineRule21({
2580
2832
  meta: {
2581
2833
  type: "suggestion",
2582
2834
  docs: {
2583
- description: "Prefer typing event target properties in the handler parameter instead of asserting them at use sites."
2835
+ description: "Prefer typing event target and detail properties in the handler parameter instead of asserting them at use sites."
2584
2836
  },
2585
2837
  messages: {
2586
- typeEventParameter: "Type `{{parameter}}.{{property}}` in the function signature instead of asserting it at the use site."
2838
+ 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."
2587
2839
  }
2588
2840
  },
2589
2841
  create(context) {
@@ -2604,9 +2856,69 @@ var preferEventParameterTypeRule = defineRule18({
2604
2856
  }
2605
2857
  });
2606
2858
 
2859
+ // src/generic/rules/prefer-lit-query-decorators.ts
2860
+ import { defineRule as defineRule22 } from "@oxlint/plugins";
2861
+ var messageIds = {
2862
+ assignedElements: "preferQueryAssignedElements",
2863
+ assignedNodes: "preferQueryAssignedNodes",
2864
+ querySelector: "preferQuery",
2865
+ querySelectorAll: "preferQueryAll"
2866
+ };
2867
+ var preferLitQueryDecoratorsRule = defineRule22({
2868
+ meta: {
2869
+ type: "suggestion",
2870
+ docs: {
2871
+ description: "Require Lit query decorators instead of static render-root queries in Lit elements."
2872
+ },
2873
+ schema: [
2874
+ {
2875
+ type: "object",
2876
+ properties: {
2877
+ querySelector: { type: "boolean" },
2878
+ querySelectorAll: { type: "boolean" },
2879
+ assignedElements: { type: "boolean" },
2880
+ assignedNodes: { type: "boolean" }
2881
+ },
2882
+ additionalProperties: false
2883
+ }
2884
+ ],
2885
+ defaultOptions: [
2886
+ {
2887
+ querySelector: true,
2888
+ querySelectorAll: true,
2889
+ assignedElements: true,
2890
+ assignedNodes: true
2891
+ }
2892
+ ],
2893
+ messages: {
2894
+ preferQuery: "Use the @query decorator instead of this.{{root}}.querySelector().",
2895
+ preferQueryAll: "Use the @queryAll decorator instead of this.{{root}}.querySelectorAll().",
2896
+ preferQueryAssignedElements: "Use the @queryAssignedElements decorator instead of this.{{root}}.querySelector().assignedElements().",
2897
+ preferQueryAssignedNodes: "Use the @queryAssignedNodes decorator instead of this.{{root}}.querySelector().assignedNodes()."
2898
+ }
2899
+ },
2900
+ create(context) {
2901
+ const bases = litElementBaseClasses(context.settings);
2902
+ const options = jsonObject(context.options[0]);
2903
+ const disabled = new Set(options ? Object.keys(options).filter((method) => options[method] === false) : []);
2904
+ return {
2905
+ CallExpression(node) {
2906
+ const query = litQueryDecoratorCall(node, bases);
2907
+ if (!query || disabled.has(query.method))
2908
+ return;
2909
+ context.report({
2910
+ node,
2911
+ messageId: messageIds[query.method],
2912
+ data: { root: query.root }
2913
+ });
2914
+ }
2915
+ };
2916
+ }
2917
+ });
2918
+
2607
2919
  // src/generic/rules/prefer-types-over-assertions.ts
2608
- import { defineRule as defineRule19 } from "@oxlint/plugins";
2609
- var preferTypesOverAssertionsRule = defineRule19({
2920
+ import { defineRule as defineRule23 } from "@oxlint/plugins";
2921
+ var preferTypesOverAssertionsRule = defineRule23({
2610
2922
  meta: {
2611
2923
  type: "problem",
2612
2924
  docs: {
@@ -2618,7 +2930,7 @@ var preferTypesOverAssertionsRule = defineRule19({
2618
2930
  },
2619
2931
  create(context) {
2620
2932
  const checkAssertion = (node) => {
2621
- if (node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const") {
2933
+ if (node.typeAnnotation.type === "TSTypeReference" && node.typeAnnotation.typeName.type === "Identifier" && node.typeAnnotation.typeName.name === "const" || assertedEventParameter(node)) {
2622
2934
  return;
2623
2935
  }
2624
2936
  context.report({ node, messageId: "avoidAssertion" });
@@ -2634,7 +2946,11 @@ var preferTypesOverAssertionsRule = defineRule19({
2634
2946
  var timmoPlugin = eslintCompatPlugin2({
2635
2947
  meta: { name: "timmo" },
2636
2948
  rules: {
2949
+ "no-explicit-inferable-type-predicate": noExplicitInferableTypePredicateRule,
2950
+ "no-unchecked-query-selector-type": noUncheckedQuerySelectorTypeRule,
2951
+ "no-untyped-custom-event": noUntypedCustomEventRule,
2637
2952
  "prefer-event-parameter-type": preferEventParameterTypeRule,
2953
+ "prefer-lit-query-decorators": preferLitQueryDecoratorsRule,
2638
2954
  "prefer-types-over-assertions": preferTypesOverAssertionsRule
2639
2955
  }
2640
2956
  });
@@ -2657,7 +2973,11 @@ var recommended = defineConfig({
2657
2973
  rules: {
2658
2974
  ...enablePluginRules("anti-slop", src_default),
2659
2975
  ...enablePluginRules("timmo", generic_default),
2660
- "anti-slop/require-safety-comment-for-type-assertion": "off"
2976
+ "anti-slop/require-safety-comment-for-type-assertion": "off",
2977
+ "typescript/no-non-null-assertion": "error",
2978
+ "typescript/no-unnecessary-type-arguments": "error",
2979
+ "typescript/no-unnecessary-type-assertion": "error",
2980
+ "typescript/no-unnecessary-type-parameters": "error"
2661
2981
  }
2662
2982
  });
2663
2983
  var recommended_default = recommended;
@@ -2666,34 +2986,10 @@ var recommended_default = recommended;
2666
2986
  import { eslintCompatPlugin as eslintCompatPlugin3 } from "@oxlint/plugins";
2667
2987
 
2668
2988
  // src/effect/rules/no-try-catch-in-effect-generators.ts
2669
- import { defineRule as defineRule20 } from "@oxlint/plugins";
2670
- function resolveVariable2(sourceCode, identifier) {
2671
- let scope = sourceCode.getScope(identifier);
2672
- while (scope) {
2673
- const variable = scope.set.get(identifier.name);
2674
- if (variable)
2675
- return variable;
2676
- scope = scope.upper;
2677
- }
2678
- return null;
2679
- }
2680
- function nearestEnclosingFunction2(node) {
2681
- let current = node.parent;
2682
- while (current) {
2683
- if (current.type === "FunctionDeclaration" || current.type === "FunctionExpression" || current.type === "ArrowFunctionExpression") {
2684
- return current;
2685
- }
2686
- current = current.parent;
2687
- }
2688
- return null;
2689
- }
2690
- function staticMemberName(node) {
2691
- if (node.type !== "MemberExpression" || node.computed)
2692
- return null;
2693
- return node.property.type === "Identifier" ? node.property.name : null;
2694
- }
2989
+ import { defineRule as defineRule24 } from "@oxlint/plugins";
2990
+ // src/shared/effect.ts
2695
2991
  function isEffectImport(sourceCode, identifier, namespace) {
2696
- const variable = resolveVariable2(sourceCode, identifier);
2992
+ const variable = resolveVariable(sourceCode, identifier);
2697
2993
  return variable?.defs.some((definition) => {
2698
2994
  if (definition.type !== "ImportBinding" || definition.parent?.type !== "ImportDeclaration" || definition.parent.source.value !== "effect") {
2699
2995
  return false;
@@ -2719,6 +3015,11 @@ function isEffectMethod(sourceCode, node, method) {
2719
3015
  }
2720
3016
  return isEffectImport(sourceCode, object.object, true);
2721
3017
  }
3018
+ function importsEffect(program) {
3019
+ 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/")));
3020
+ }
3021
+
3022
+ // src/effect/rules/no-try-catch-in-effect-generators.ts
2722
3023
  function isDirectArgument(owner, call) {
2723
3024
  return call.arguments.some((argument) => argument === owner);
2724
3025
  }
@@ -2732,7 +3033,7 @@ function isRecognisedEffectGenerator(sourceCode, owner) {
2732
3033
  const factoryCall = parent.callee;
2733
3034
  return factoryCall.type === "CallExpression" && isEffectMethod(sourceCode, factoryCall.callee, "fn");
2734
3035
  }
2735
- var noTryCatchInEffectGeneratorsRule = defineRule20({
3036
+ var noTryCatchInEffectGeneratorsRule = defineRule24({
2736
3037
  meta: {
2737
3038
  type: "problem",
2738
3039
  docs: {
@@ -2747,7 +3048,7 @@ var noTryCatchInEffectGeneratorsRule = defineRule20({
2747
3048
  TryStatement(node) {
2748
3049
  if (!node.handler)
2749
3050
  return;
2750
- const owner = nearestEnclosingFunction2(node);
3051
+ const owner = nearestEnclosingFunction(node);
2751
3052
  if (!owner?.generator || !isRecognisedEffectGenerator(context.sourceCode, owner)) {
2752
3053
  return;
2753
3054
  }
@@ -2757,11 +3058,85 @@ var noTryCatchInEffectGeneratorsRule = defineRule20({
2757
3058
  }
2758
3059
  });
2759
3060
 
3061
+ // src/effect/rules/prefer-platform-services.ts
3062
+ import { defineRule as defineRule25 } from "@oxlint/plugins";
3063
+ var moduleServices = new Map([
3064
+ ["child_process", "childProcess"],
3065
+ ["fs", "fileSystem"],
3066
+ ["fs/promises", "fileSystem"]
3067
+ ]);
3068
+ var bunServices = new Map([
3069
+ ["$", "childProcess"],
3070
+ ["file", "fileSystem"],
3071
+ ["spawn", "childProcess"],
3072
+ ["spawnSync", "childProcess"],
3073
+ ["write", "fileSystem"]
3074
+ ]);
3075
+ var preferPlatformServicesRule = defineRule25({
3076
+ meta: {
3077
+ type: "suggestion",
3078
+ docs: {
3079
+ description: "Prefer Effect's ChildProcessSpawner and FileSystem services over Node or Bun process and file system APIs in modules that use Effect."
3080
+ },
3081
+ messages: {
3082
+ childProcess: "Use Effect's `ChildProcessSpawner` service (`effect/process`) instead of `{{api}}`, so scope cleanup, interruption, and test layers apply.",
3083
+ fileSystem: "Use Effect's `FileSystem` service instead of `{{api}}`, so failures stay typed and tests can provide a layer."
3084
+ }
3085
+ },
3086
+ create(context) {
3087
+ let enabled = false;
3088
+ return {
3089
+ Program(node) {
3090
+ enabled = importsEffect(node);
3091
+ },
3092
+ ImportDeclaration(node) {
3093
+ if (!enabled || node.importKind === "type")
3094
+ return;
3095
+ const source = node.source.value;
3096
+ const service = moduleServices.get(source.replace(/^node:/u, ""));
3097
+ if (service) {
3098
+ context.report({ node, messageId: service, data: { api: source } });
3099
+ return;
3100
+ }
3101
+ if (source !== "bun")
3102
+ return;
3103
+ for (const specifier of node.specifiers) {
3104
+ if (specifier.type !== "ImportSpecifier" || specifier.importKind === "type") {
3105
+ continue;
3106
+ }
3107
+ const imported = specifier.imported.type === "Identifier" ? specifier.imported.name : specifier.imported.value;
3108
+ const bunService = bunServices.get(imported);
3109
+ if (bunService) {
3110
+ context.report({
3111
+ node: specifier,
3112
+ messageId: bunService,
3113
+ data: { api: `Bun.${imported}` }
3114
+ });
3115
+ }
3116
+ }
3117
+ },
3118
+ MemberExpression(node) {
3119
+ const name = staticMemberName(node);
3120
+ const service = name ? bunServices.get(name) : undefined;
3121
+ if (!enabled || !service || node.object.type !== "Identifier" || node.object.name !== "Bun" || (resolveVariable(context.sourceCode, node.object)?.defs.length ?? 0) > 0) {
3122
+ return;
3123
+ }
3124
+ context.report({
3125
+ node,
3126
+ messageId: service,
3127
+ data: { api: `Bun.${name}` }
3128
+ });
3129
+ }
3130
+ };
3131
+ }
3132
+ });
3133
+
2760
3134
  // src/effect/index.ts
2761
3135
  var timmoEffectPlugin = eslintCompatPlugin3({
2762
3136
  meta: { name: "timmo-effect" },
2763
3137
  rules: {
2764
- "no-try-catch-in-effect-generators": noTryCatchInEffectGeneratorsRule
3138
+ "no-try-catch-in-effect-generators": noTryCatchInEffectGeneratorsRule,
3139
+ "prefer-platform-services": preferPlatformServicesRule
2765
3140
  }
2766
3141
  });
2767
3142
  var effect_default = timmoEffectPlugin;
@@ -2770,7 +3145,7 @@ var effect_default = timmoEffectPlugin;
2770
3145
  import { eslintCompatPlugin as eslintCompatPlugin4 } from "@oxlint/plugins";
2771
3146
 
2772
3147
  // vendor/anti-slop/src/effect/rules/no-manual-effect-error-tag.ts
2773
- import { defineRule as defineRule21 } from "@oxlint/plugins";
3148
+ import { defineRule as defineRule26 } from "@oxlint/plugins";
2774
3149
 
2775
3150
  // vendor/anti-slop/src/effect/shared/tagged-values.ts
2776
3151
  var equalityOperators = new Set(["==", "===", "!=", "!=="]);
@@ -2817,7 +3192,7 @@ var isMatchPatternObject = (node) => {
2817
3192
  };
2818
3193
 
2819
3194
  // vendor/anti-slop/src/effect/rules/no-manual-effect-error-tag.ts
2820
- var noManualEffectErrorTagRule = defineRule21({
3195
+ var noManualEffectErrorTagRule = defineRule26({
2821
3196
  meta: {
2822
3197
  type: "problem",
2823
3198
  docs: {
@@ -2854,8 +3229,8 @@ var noManualEffectErrorTagRule = defineRule21({
2854
3229
  });
2855
3230
 
2856
3231
  // vendor/anti-slop/src/effect/rules/no-manual-tag-comparison.ts
2857
- import { defineRule as defineRule22 } from "@oxlint/plugins";
2858
- var noManualTagComparisonRule = defineRule22({
3232
+ import { defineRule as defineRule27 } from "@oxlint/plugins";
3233
+ var noManualTagComparisonRule = defineRule27({
2859
3234
  meta: {
2860
3235
  type: "problem",
2861
3236
  docs: {
@@ -2885,8 +3260,8 @@ var noManualTagComparisonRule = defineRule22({
2885
3260
  });
2886
3261
 
2887
3262
  // vendor/anti-slop/src/effect/rules/no-manual-tagged-construction.ts
2888
- import { defineRule as defineRule23 } from "@oxlint/plugins";
2889
- var noManualTaggedConstructionRule = defineRule23({
3263
+ import { defineRule as defineRule28 } from "@oxlint/plugins";
3264
+ var noManualTaggedConstructionRule = defineRule28({
2890
3265
  meta: {
2891
3266
  type: "problem",
2892
3267
  docs: {
@@ -2911,7 +3286,7 @@ var noManualTaggedConstructionRule = defineRule23({
2911
3286
  });
2912
3287
 
2913
3288
  // vendor/anti-slop/src/effect/rules/no-service-constructor-imports.ts
2914
- import { defineRule as defineRule24 } from "@oxlint/plugins";
3289
+ import { defineRule as defineRule29 } from "@oxlint/plugins";
2915
3290
  var SERVICE_CONSTRUCTOR_NAME = /^make[A-Z]/u;
2916
3291
  var TEST_FILE = /\.(?:test|spec)\.[cm]?[jt]sx?$/u;
2917
3292
  function isProjectLocalImport(source) {
@@ -2922,7 +3297,7 @@ function getImportedName(specifier) {
2922
3297
  return specifier.imported.name;
2923
3298
  return specifier.imported.value;
2924
3299
  }
2925
- var noServiceConstructorImportsRule = defineRule24({
3300
+ var noServiceConstructorImportsRule = defineRule29({
2926
3301
  meta: {
2927
3302
  type: "problem",
2928
3303
  docs: {
@@ -2956,9 +3331,9 @@ var noServiceConstructorImportsRule = defineRule24({
2956
3331
  });
2957
3332
 
2958
3333
  // vendor/anti-slop/src/effect/rules/prefer-effect-match.ts
2959
- import { defineRule as defineRule25 } from "@oxlint/plugins";
3334
+ import { defineRule as defineRule30 } from "@oxlint/plugins";
2960
3335
  var equalityOperators2 = new Set(["==", "===", "!=", "!=="]);
2961
- var preferEffectMatchRule = defineRule25({
3336
+ var preferEffectMatchRule = defineRule30({
2962
3337
  meta: {
2963
3338
  type: "problem",
2964
3339
  docs: {
@@ -3033,7 +3408,7 @@ var recommendedEffect = defineConfig({
3033
3408
  var recommended_effect_default = recommendedEffect;
3034
3409
 
3035
3410
  // src/install/copy.ts
3036
- import { cp, mkdir, readdir, rm } from "node:fs/promises";
3411
+ import { cp, mkdir, readdir, rm, writeFile } from "node:fs/promises";
3037
3412
  import { dirname, join, relative, resolve, sep } from "node:path";
3038
3413
  import { fileURLToPath } from "node:url";
3039
3414
  var DEFAULT_COPY_DESTINATION = "tools/oxlint/timmo-rules";
@@ -3070,6 +3445,10 @@ async function copyRules(destination, options = {}) {
3070
3445
  await cp(join(root, "LICENSE"), join(target, "effect/LICENSE"));
3071
3446
  await copyTree(join(root, "src/generic"), join(target, "generic"));
3072
3447
  await cp(join(root, "LICENSE"), join(target, "generic/LICENSE"));
3448
+ await copyTree(join(root, "src/shared"), join(target, "shared"));
3449
+ await cp(join(root, "LICENSE"), join(target, "shared/LICENSE"));
3450
+ await writeFile(join(target, "shared/scope.ts"), `export { resolveVariable } from "../upstream/anti-slop/shared/scope.ts";
3451
+ `);
3073
3452
  const displayRoot = relative(process.cwd(), target) || ".";
3074
3453
  const entryPoint = (path) => `./${path.split(sep).join("/")}`;
3075
3454
  return {