@effect/language-service 0.80.0 → 0.81.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/cli.js CHANGED
@@ -25719,7 +25719,7 @@ var runWith2 = (command, config) => {
25719
25719
  // package.json
25720
25720
  var package_default = {
25721
25721
  name: "@effect/language-service",
25722
- version: "0.80.0",
25722
+ version: "0.81.0",
25723
25723
  publishConfig: {
25724
25724
  access: "public",
25725
25725
  directory: "dist"
@@ -31800,6 +31800,7 @@ var anyUnknownInErrorContext = createDiagnostic({
31800
31800
  name: "anyUnknownInErrorContext",
31801
31801
  code: 28,
31802
31802
  description: "Detects 'any' or 'unknown' types in Effect error or requirements channels",
31803
+ group: "correctness",
31803
31804
  severity: "off",
31804
31805
  fixable: false,
31805
31806
  supportedEffect: ["v3", "v4"],
@@ -31905,6 +31906,7 @@ var catchAllToMapError = createDiagnostic({
31905
31906
  name: "catchAllToMapError",
31906
31907
  code: 39,
31907
31908
  description: "Suggests using Effect.mapError instead of Effect.catchAll when the callback only wraps the error with Effect.fail",
31909
+ group: "style",
31908
31910
  severity: "suggestion",
31909
31911
  fixable: true,
31910
31912
  supportedEffect: ["v3", "v4"],
@@ -32004,6 +32006,7 @@ var catchUnfailableEffect = createDiagnostic({
32004
32006
  name: "catchUnfailableEffect",
32005
32007
  code: 2,
32006
32008
  description: "Warns when using error handling on Effects that never fail (error type is 'never')",
32009
+ group: "antipattern",
32007
32010
  severity: "suggestion",
32008
32011
  fixable: false,
32009
32012
  supportedEffect: ["v3", "v4"],
@@ -32055,6 +32058,7 @@ var classSelfMismatch = createDiagnostic({
32055
32058
  name: "classSelfMismatch",
32056
32059
  code: 20,
32057
32060
  description: "Ensures Self type parameter matches the class name in Service/Tag/Schema classes",
32061
+ group: "correctness",
32058
32062
  severity: "error",
32059
32063
  fixable: true,
32060
32064
  supportedEffect: ["v3", "v4"],
@@ -32195,6 +32199,7 @@ var deterministicKeys = createDiagnostic({
32195
32199
  name: "deterministicKeys",
32196
32200
  code: 25,
32197
32201
  description: "Enforces deterministic naming for service/tag/error identifiers based on class names",
32202
+ group: "style",
32198
32203
  severity: "off",
32199
32204
  fixable: true,
32200
32205
  supportedEffect: ["v3", "v4"],
@@ -32314,6 +32319,7 @@ var duplicatePackage = createDiagnostic({
32314
32319
  name: "duplicatePackage",
32315
32320
  code: 6,
32316
32321
  description: "Detects when multiple versions of the same Effect package are loaded",
32322
+ group: "correctness",
32317
32323
  severity: "warning",
32318
32324
  fixable: false,
32319
32325
  supportedEffect: ["v3", "v4"],
@@ -32345,6 +32351,7 @@ var effectFnIife = createDiagnostic({
32345
32351
  name: "effectFnIife",
32346
32352
  code: 46,
32347
32353
  description: "Effect.fn or Effect.fnUntraced is called as an IIFE (Immediately Invoked Function Expression). Use Effect.gen instead.",
32354
+ group: "antipattern",
32348
32355
  severity: "warning",
32349
32356
  fixable: true,
32350
32357
  supportedEffect: ["v3", "v4"],
@@ -32449,6 +32456,7 @@ var effectFnOpportunity = createDiagnostic({
32449
32456
  name: "effectFnOpportunity",
32450
32457
  code: 41,
32451
32458
  description: "Suggests using Effect.fn for functions that returns an Effect",
32459
+ group: "style",
32452
32460
  severity: "suggestion",
32453
32461
  fixable: true,
32454
32462
  supportedEffect: ["v3", "v4"],
@@ -33043,6 +33051,7 @@ var effectGenUsesAdapter = createDiagnostic({
33043
33051
  name: "effectGenUsesAdapter",
33044
33052
  code: 23,
33045
33053
  description: "Warns when using the deprecated adapter parameter in Effect.gen",
33054
+ group: "antipattern",
33046
33055
  severity: "warning",
33047
33056
  fixable: false,
33048
33057
  supportedEffect: ["v3", "v4"],
@@ -33083,6 +33092,7 @@ var effectInFailure = createDiagnostic({
33083
33092
  name: "effectInFailure",
33084
33093
  code: 49,
33085
33094
  description: "Warns when an Effect is used inside an Effect failure channel",
33095
+ group: "antipattern",
33086
33096
  severity: "warning",
33087
33097
  fixable: false,
33088
33098
  supportedEffect: ["v3", "v4"],
@@ -33149,6 +33159,7 @@ var effectInVoidSuccess = createDiagnostic({
33149
33159
  name: "effectInVoidSuccess",
33150
33160
  code: 14,
33151
33161
  description: "Detects nested Effects in void success channels that may cause unexecuted effects",
33162
+ group: "antipattern",
33152
33163
  severity: "warning",
33153
33164
  fixable: false,
33154
33165
  supportedEffect: ["v3", "v4"],
@@ -33200,6 +33211,7 @@ var effectMapVoid = createDiagnostic({
33200
33211
  name: "effectMapVoid",
33201
33212
  code: 40,
33202
33213
  description: "Suggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {})",
33214
+ group: "style",
33203
33215
  severity: "suggestion",
33204
33216
  fixable: true,
33205
33217
  supportedEffect: ["v3", "v4"],
@@ -33266,6 +33278,7 @@ var effectSucceedWithVoid = createDiagnostic({
33266
33278
  name: "effectSucceedWithVoid",
33267
33279
  code: 47,
33268
33280
  description: "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
33281
+ group: "style",
33269
33282
  severity: "suggestion",
33270
33283
  fixable: true,
33271
33284
  supportedEffect: ["v3", "v4"],
@@ -33319,6 +33332,7 @@ var extendsNativeError = createDiagnostic({
33319
33332
  name: "extendsNativeError",
33320
33333
  code: 50,
33321
33334
  description: "Warns when a class directly extends the native Error class",
33335
+ group: "effectNative",
33322
33336
  severity: "off",
33323
33337
  fixable: false,
33324
33338
  supportedEffect: ["v3", "v4"],
@@ -33371,6 +33385,7 @@ var floatingEffect = createDiagnostic({
33371
33385
  name: "floatingEffect",
33372
33386
  code: 3,
33373
33387
  description: "Ensures Effects are yielded or assigned to variables, not left floating",
33388
+ group: "correctness",
33374
33389
  severity: "error",
33375
33390
  fixable: false,
33376
33391
  supportedEffect: ["v3", "v4"],
@@ -33424,6 +33439,7 @@ var genericEffectServices = createDiagnostic({
33424
33439
  name: "genericEffectServices",
33425
33440
  code: 10,
33426
33441
  description: "Prevents services with type parameters that cannot be discriminated at runtime",
33442
+ group: "correctness",
33427
33443
  severity: "warning",
33428
33444
  fixable: false,
33429
33445
  supportedEffect: ["v3", "v4"],
@@ -33473,6 +33489,7 @@ var globalErrorInEffectCatch = createDiagnostic({
33473
33489
  name: "globalErrorInEffectCatch",
33474
33490
  code: 36,
33475
33491
  description: "Warns when catch callbacks return global Error type instead of typed errors",
33492
+ group: "antipattern",
33476
33493
  severity: "warning",
33477
33494
  fixable: false,
33478
33495
  supportedEffect: ["v3", "v4"],
@@ -33535,6 +33552,7 @@ var globalErrorInEffectFailure = createDiagnostic({
33535
33552
  name: "globalErrorInEffectFailure",
33536
33553
  code: 35,
33537
33554
  description: "Warns when the global Error type is used in an Effect failure channel",
33555
+ group: "antipattern",
33538
33556
  severity: "warning",
33539
33557
  fixable: false,
33540
33558
  supportedEffect: ["v3", "v4"],
@@ -33585,11 +33603,54 @@ var globalErrorInEffectFailure = createDiagnostic({
33585
33603
  })
33586
33604
  });
33587
33605
 
33606
+ // src/diagnostics/globalFetch.ts
33607
+ var globalFetch = createDiagnostic({
33608
+ name: "globalFetch",
33609
+ code: 53,
33610
+ description: "Warns when using the global fetch function instead of the Effect HTTP client",
33611
+ group: "effectNative",
33612
+ severity: "off",
33613
+ fixable: false,
33614
+ supportedEffect: ["v3", "v4"],
33615
+ apply: fn3("globalFetch.apply")(function* (sourceFile, report) {
33616
+ const ts = yield* service2(TypeScriptApi);
33617
+ const typeChecker = yield* service2(TypeCheckerApi);
33618
+ const typeParser = yield* service2(TypeParser);
33619
+ const fetchSymbol = typeChecker.resolveName("fetch", void 0, ts.SymbolFlags.Value, false);
33620
+ if (!fetchSymbol) return;
33621
+ const effectVersion = typeParser.supportedEffect();
33622
+ const packageName = effectVersion === "v3" ? "@effect/platform" : "effect/unstable/http";
33623
+ const messageText = `Prefer using HttpClient from ${packageName} instead of the global 'fetch' function.`;
33624
+ const nodeToVisit = [];
33625
+ const appendNodeToVisit = (node) => {
33626
+ nodeToVisit.push(node);
33627
+ return void 0;
33628
+ };
33629
+ ts.forEachChild(sourceFile, appendNodeToVisit);
33630
+ while (nodeToVisit.length > 0) {
33631
+ const node = nodeToVisit.shift();
33632
+ if (ts.isCallExpression(node)) {
33633
+ const symbol4 = typeChecker.getSymbolAtLocation(node.expression);
33634
+ const resolvedSymbol = symbol4 && symbol4.flags & ts.SymbolFlags.Alias ? typeChecker.getAliasedSymbol(symbol4) : symbol4;
33635
+ if (resolvedSymbol === fetchSymbol) {
33636
+ report({
33637
+ location: node.expression,
33638
+ messageText,
33639
+ fixes: []
33640
+ });
33641
+ }
33642
+ }
33643
+ ts.forEachChild(node, appendNodeToVisit);
33644
+ }
33645
+ })
33646
+ });
33647
+
33588
33648
  // src/diagnostics/importFromBarrel.ts
33589
33649
  var importFromBarrel = createDiagnostic({
33590
33650
  name: "importFromBarrel",
33591
33651
  code: 12,
33592
33652
  description: "Suggests importing from specific module paths instead of barrel exports",
33653
+ group: "style",
33593
33654
  severity: "off",
33594
33655
  fixable: true,
33595
33656
  supportedEffect: ["v3", "v4"],
@@ -33732,6 +33793,7 @@ var instanceOfSchema = createDiagnostic({
33732
33793
  name: "instanceOfSchema",
33733
33794
  code: 45,
33734
33795
  description: "Suggests using Schema.is instead of instanceof for Effect Schema types",
33796
+ group: "effectNative",
33735
33797
  severity: "off",
33736
33798
  fixable: true,
33737
33799
  supportedEffect: ["v3", "v4"],
@@ -33797,6 +33859,7 @@ var layerMergeAllWithDependencies = createDiagnostic({
33797
33859
  name: "layerMergeAllWithDependencies",
33798
33860
  code: 37,
33799
33861
  description: "Detects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires",
33862
+ group: "antipattern",
33800
33863
  severity: "warning",
33801
33864
  fixable: true,
33802
33865
  supportedEffect: ["v3", "v4"],
@@ -33912,6 +33975,7 @@ var leakingRequirements = createDiagnostic({
33912
33975
  name: "leakingRequirements",
33913
33976
  code: 8,
33914
33977
  description: "Detects implementation services leaked in service methods",
33978
+ group: "antipattern",
33915
33979
  severity: "suggestion",
33916
33980
  fixable: false,
33917
33981
  supportedEffect: ["v3", "v4"],
@@ -34068,6 +34132,7 @@ var missedPipeableOpportunity = createDiagnostic({
34068
34132
  name: "missedPipeableOpportunity",
34069
34133
  code: 26,
34070
34134
  description: "Enforces the use of pipeable style for nested function calls",
34135
+ group: "style",
34071
34136
  severity: "off",
34072
34137
  fixable: true,
34073
34138
  supportedEffect: ["v3", "v4"],
@@ -34250,6 +34315,7 @@ var missingEffectContext = createDiagnostic({
34250
34315
  name: "missingEffectContext",
34251
34316
  code: 1,
34252
34317
  description: "Reports missing service requirements in Effect context channel",
34318
+ group: "correctness",
34253
34319
  severity: "error",
34254
34320
  fixable: false,
34255
34321
  supportedEffect: ["v3", "v4"],
@@ -34301,6 +34367,7 @@ var missingEffectError = createDiagnostic({
34301
34367
  name: "missingEffectError",
34302
34368
  code: 1,
34303
34369
  description: "Reports missing error types in Effect error channel",
34370
+ group: "correctness",
34304
34371
  severity: "error",
34305
34372
  fixable: true,
34306
34373
  supportedEffect: ["v3", "v4"],
@@ -34444,6 +34511,7 @@ var missingEffectServiceDependency = createDiagnostic({
34444
34511
  name: "missingEffectServiceDependency",
34445
34512
  code: 22,
34446
34513
  description: "Checks that Effect.Service dependencies satisfy all required layer inputs",
34514
+ group: "style",
34447
34515
  severity: "off",
34448
34516
  fixable: false,
34449
34517
  supportedEffect: ["v3"],
@@ -34540,6 +34608,7 @@ var missingLayerContext = createDiagnostic({
34540
34608
  name: "missingLayerContext",
34541
34609
  code: 38,
34542
34610
  description: "Reports missing service requirements in Layer context channel",
34611
+ group: "correctness",
34543
34612
  severity: "error",
34544
34613
  fixable: false,
34545
34614
  supportedEffect: ["v3", "v4"],
@@ -34591,6 +34660,7 @@ var missingReturnYieldStar = createDiagnostic({
34591
34660
  name: "missingReturnYieldStar",
34592
34661
  code: 7,
34593
34662
  description: "Suggests using 'return yield*' for Effects with never success for better type narrowing",
34663
+ group: "correctness",
34594
34664
  severity: "error",
34595
34665
  fixable: true,
34596
34666
  supportedEffect: ["v3", "v4"],
@@ -34643,6 +34713,7 @@ var missingStarInYieldEffectGen = createDiagnostic({
34643
34713
  name: "missingStarInYieldEffectGen",
34644
34714
  code: 4,
34645
34715
  description: "Enforces using 'yield*' instead of 'yield' when yielding Effects in generators",
34716
+ group: "correctness",
34646
34717
  severity: "error",
34647
34718
  fixable: true,
34648
34719
  supportedEffect: ["v3", "v4"],
@@ -34720,6 +34791,7 @@ var multipleEffectProvide = createDiagnostic({
34720
34791
  name: "multipleEffectProvide",
34721
34792
  code: 18,
34722
34793
  description: "Warns against chaining Effect.provide calls which can cause service lifecycle issues",
34794
+ group: "antipattern",
34723
34795
  severity: "warning",
34724
34796
  fixable: true,
34725
34797
  supportedEffect: ["v3", "v4"],
@@ -34822,7 +34894,11 @@ var moduleAlternativesV3 = /* @__PURE__ */ new Map([
34822
34894
  ["path/win32", { alternative: "Path", module: "path", package: "@effect/platform" }],
34823
34895
  ["node:path/win32", { alternative: "Path", module: "path", package: "@effect/platform" }],
34824
34896
  ["child_process", { alternative: "CommandExecutor", module: "child_process", package: "@effect/platform" }],
34825
- ["node:child_process", { alternative: "CommandExecutor", module: "child_process", package: "@effect/platform" }]
34897
+ ["node:child_process", { alternative: "CommandExecutor", module: "child_process", package: "@effect/platform" }],
34898
+ ["http", { alternative: "HttpClient", module: "http", package: "@effect/platform" }],
34899
+ ["node:http", { alternative: "HttpClient", module: "http", package: "@effect/platform" }],
34900
+ ["https", { alternative: "HttpClient", module: "https", package: "@effect/platform" }],
34901
+ ["node:https", { alternative: "HttpClient", module: "https", package: "@effect/platform" }]
34826
34902
  ]);
34827
34903
  var moduleAlternativesV4 = /* @__PURE__ */ new Map([
34828
34904
  ["fs", { alternative: "FileSystem", module: "fs", package: "effect" }],
@@ -34836,12 +34912,17 @@ var moduleAlternativesV4 = /* @__PURE__ */ new Map([
34836
34912
  ["path/win32", { alternative: "Path", module: "path", package: "effect" }],
34837
34913
  ["node:path/win32", { alternative: "Path", module: "path", package: "effect" }],
34838
34914
  ["child_process", { alternative: "ChildProcess", module: "child_process", package: "effect" }],
34839
- ["node:child_process", { alternative: "ChildProcess", module: "child_process", package: "effect" }]
34915
+ ["node:child_process", { alternative: "ChildProcess", module: "child_process", package: "effect" }],
34916
+ ["http", { alternative: "HttpClient", module: "http", package: "effect/unstable/http" }],
34917
+ ["node:http", { alternative: "HttpClient", module: "http", package: "effect/unstable/http" }],
34918
+ ["https", { alternative: "HttpClient", module: "https", package: "effect/unstable/http" }],
34919
+ ["node:https", { alternative: "HttpClient", module: "https", package: "effect/unstable/http" }]
34840
34920
  ]);
34841
34921
  var nodeBuiltinImport = createDiagnostic({
34842
34922
  name: "nodeBuiltinImport",
34843
34923
  code: 52,
34844
34924
  description: "Warns when importing Node.js built-in modules that have Effect-native counterparts",
34925
+ group: "effectNative",
34845
34926
  severity: "off",
34846
34927
  fixable: false,
34847
34928
  supportedEffect: ["v3", "v4"],
@@ -34885,6 +34966,7 @@ var nonObjectEffectServiceType = createDiagnostic({
34885
34966
  name: "nonObjectEffectServiceType",
34886
34967
  code: 24,
34887
34968
  description: "Ensures Effect.Service types are objects, not primitives",
34969
+ group: "correctness",
34888
34970
  severity: "error",
34889
34971
  fixable: false,
34890
34972
  supportedEffect: ["v3"],
@@ -35669,6 +35751,7 @@ var outdatedApi = createDiagnostic({
35669
35751
  name: "outdatedApi",
35670
35752
  code: 48,
35671
35753
  description: "Detects usage of APIs that have been removed or renamed in Effect v4",
35754
+ group: "correctness",
35672
35755
  severity: "warning",
35673
35756
  fixable: false,
35674
35757
  supportedEffect: ["v4"],
@@ -35738,6 +35821,7 @@ var outdatedEffectCodegen = createDiagnostic({
35738
35821
  name: "outdatedEffectCodegen",
35739
35822
  code: 19,
35740
35823
  description: "Detects when generated code is outdated and needs to be regenerated",
35824
+ group: "correctness",
35741
35825
  severity: "warning",
35742
35826
  fixable: true,
35743
35827
  supportedEffect: ["v3", "v4"],
@@ -35786,6 +35870,7 @@ var overriddenSchemaConstructor = createDiagnostic({
35786
35870
  name: "overriddenSchemaConstructor",
35787
35871
  code: 30,
35788
35872
  description: "Prevents overriding constructors in Schema classes which breaks decoding behavior",
35873
+ group: "correctness",
35789
35874
  severity: "error",
35790
35875
  fixable: true,
35791
35876
  supportedEffect: ["v3", "v4"],
@@ -35925,6 +36010,7 @@ var preferSchemaOverJson = createDiagnostic({
35925
36010
  name: "preferSchemaOverJson",
35926
36011
  code: 44,
35927
36012
  description: "Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify which may throw",
36013
+ group: "effectNative",
35928
36014
  severity: "suggestion",
35929
36015
  fixable: false,
35930
36016
  supportedEffect: ["v3", "v4"],
@@ -36037,6 +36123,7 @@ var redundantSchemaTagIdentifier = createDiagnostic({
36037
36123
  name: "redundantSchemaTagIdentifier",
36038
36124
  code: 42,
36039
36125
  description: "Suggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest",
36126
+ group: "style",
36040
36127
  severity: "suggestion",
36041
36128
  fixable: true,
36042
36129
  supportedEffect: ["v3", "v4"],
@@ -36089,6 +36176,7 @@ var returnEffectInGen = createDiagnostic({
36089
36176
  name: "returnEffectInGen",
36090
36177
  code: 11,
36091
36178
  description: "Warns when returning an Effect in a generator causes nested Effect<Effect<...>>",
36179
+ group: "antipattern",
36092
36180
  severity: "suggestion",
36093
36181
  fixable: true,
36094
36182
  supportedEffect: ["v3", "v4"],
@@ -36160,6 +36248,7 @@ var runEffectInsideEffect = createDiagnostic({
36160
36248
  name: "runEffectInsideEffect",
36161
36249
  code: 32,
36162
36250
  description: "Suggests using Runtime methods instead of Effect.run* inside Effect contexts",
36251
+ group: "antipattern",
36163
36252
  severity: "suggestion",
36164
36253
  fixable: true,
36165
36254
  supportedEffect: ["v3"],
@@ -36286,6 +36375,7 @@ var schemaStructWithTag = createDiagnostic({
36286
36375
  name: "schemaStructWithTag",
36287
36376
  code: 34,
36288
36377
  description: "Suggests using Schema.TaggedStruct instead of Schema.Struct with _tag field",
36378
+ group: "style",
36289
36379
  severity: "suggestion",
36290
36380
  fixable: true,
36291
36381
  supportedEffect: ["v3", "v4"],
@@ -36380,9 +36470,10 @@ var schemaSyncInEffect = createDiagnostic({
36380
36470
  name: "schemaSyncInEffect",
36381
36471
  code: 43,
36382
36472
  description: "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
36473
+ group: "antipattern",
36383
36474
  severity: "suggestion",
36384
36475
  fixable: false,
36385
- supportedEffect: ["v3", "v4"],
36476
+ supportedEffect: ["v3"],
36386
36477
  apply: fn3("schemaSyncInEffect.apply")(function* (sourceFile, report) {
36387
36478
  const ts = yield* service2(TypeScriptApi);
36388
36479
  const typeParser = yield* service2(TypeParser);
@@ -36431,6 +36522,7 @@ var schemaUnionOfLiterals = createDiagnostic({
36431
36522
  name: "schemaUnionOfLiterals",
36432
36523
  code: 33,
36433
36524
  description: "Simplifies Schema.Union of multiple Schema.Literal calls into single Schema.Literal",
36525
+ group: "style",
36434
36526
  severity: "off",
36435
36527
  fixable: true,
36436
36528
  supportedEffect: ["v3"],
@@ -36508,6 +36600,7 @@ var scopeInLayerEffect = createDiagnostic({
36508
36600
  name: "scopeInLayerEffect",
36509
36601
  code: 13,
36510
36602
  description: "Suggests using Layer.scoped instead of Layer.effect when Scope is in requirements",
36603
+ group: "antipattern",
36511
36604
  severity: "warning",
36512
36605
  fixable: true,
36513
36606
  supportedEffect: ["v3"],
@@ -36605,6 +36698,7 @@ var serviceNotAsClass = createDiagnostic({
36605
36698
  name: "serviceNotAsClass",
36606
36699
  code: 51,
36607
36700
  description: "Warns when ServiceMap.Service is used as a variable instead of a class declaration",
36701
+ group: "style",
36608
36702
  severity: "off",
36609
36703
  fixable: true,
36610
36704
  supportedEffect: ["v4"],
@@ -36682,6 +36776,7 @@ var strictBooleanExpressions = createDiagnostic({
36682
36776
  name: "strictBooleanExpressions",
36683
36777
  code: 17,
36684
36778
  description: "Enforces boolean types in conditional expressions for type safety",
36779
+ group: "style",
36685
36780
  severity: "off",
36686
36781
  fixable: false,
36687
36782
  supportedEffect: ["v3", "v4"],
@@ -36755,6 +36850,7 @@ var strictEffectProvide = createDiagnostic({
36755
36850
  name: "strictEffectProvide",
36756
36851
  code: 27,
36757
36852
  description: "Warns when using Effect.provide with layers outside of application entry points",
36853
+ group: "antipattern",
36758
36854
  severity: "off",
36759
36855
  fixable: false,
36760
36856
  supportedEffect: ["v3", "v4"],
@@ -36808,6 +36904,7 @@ var tryCatchInEffectGen = createDiagnostic({
36808
36904
  name: "tryCatchInEffectGen",
36809
36905
  code: 15,
36810
36906
  description: "Discourages try/catch in Effect generators in favor of Effect error handling",
36907
+ group: "antipattern",
36811
36908
  severity: "suggestion",
36812
36909
  fixable: false,
36813
36910
  supportedEffect: ["v3", "v4"],
@@ -36867,6 +36964,7 @@ var unknownInEffectCatch = createDiagnostic({
36867
36964
  name: "unknownInEffectCatch",
36868
36965
  code: 31,
36869
36966
  description: "Warns when catch callbacks return unknown instead of typed errors",
36967
+ group: "antipattern",
36870
36968
  severity: "warning",
36871
36969
  fixable: false,
36872
36970
  supportedEffect: ["v3", "v4"],
@@ -36930,6 +37028,7 @@ var unnecessaryEffectGen = createDiagnostic({
36930
37028
  name: "unnecessaryEffectGen",
36931
37029
  code: 5,
36932
37030
  description: "Suggests removing Effect.gen when it contains only a single return statement",
37031
+ group: "style",
36933
37032
  severity: "suggestion",
36934
37033
  fixable: true,
36935
37034
  supportedEffect: ["v3", "v4"],
@@ -36976,6 +37075,7 @@ var unnecessaryFailYieldableError = createDiagnostic({
36976
37075
  name: "unnecessaryFailYieldableError",
36977
37076
  code: 29,
36978
37077
  description: "Suggests yielding yieldable errors directly instead of wrapping with Effect.fail",
37078
+ group: "style",
36979
37079
  severity: "suggestion",
36980
37080
  fixable: true,
36981
37081
  supportedEffect: ["v3", "v4"],
@@ -37037,6 +37137,7 @@ var unnecessaryPipe = createDiagnostic({
37037
37137
  name: "unnecessaryPipe",
37038
37138
  code: 9,
37039
37139
  description: "Removes pipe calls with no arguments",
37140
+ group: "style",
37040
37141
  severity: "suggestion",
37041
37142
  fixable: true,
37042
37143
  supportedEffect: ["v3", "v4"],
@@ -37085,6 +37186,7 @@ var unnecessaryPipeChain = createDiagnostic({
37085
37186
  name: "unnecessaryPipeChain",
37086
37187
  code: 16,
37087
37188
  description: "Simplifies chained pipe calls into a single pipe call",
37189
+ group: "style",
37088
37190
  severity: "suggestion",
37089
37191
  fixable: true,
37090
37192
  supportedEffect: ["v3", "v4"],
@@ -37162,6 +37264,7 @@ var unsupportedServiceAccessors = createDiagnostic({
37162
37264
  name: "unsupportedServiceAccessors",
37163
37265
  code: 21,
37164
37266
  description: "Warns about service accessors that need codegen due to generic/complex signatures",
37267
+ group: "correctness",
37165
37268
  severity: "warning",
37166
37269
  fixable: true,
37167
37270
  supportedEffect: ["v3", "v4"],
@@ -37239,6 +37342,7 @@ var diagnostics = [
37239
37342
  leakingRequirements,
37240
37343
  unnecessaryPipe,
37241
37344
  genericEffectServices,
37345
+ globalFetch,
37242
37346
  returnEffectInGen,
37243
37347
  tryCatchInEffectGen,
37244
37348
  importFromBarrel,
@@ -38201,13 +38305,6 @@ var GREEN = "\x1B[0;32m";
38201
38305
  var YELLOW = "\x1B[0;33m";
38202
38306
  var BLUE = "\x1B[0;34m";
38203
38307
  var CYAN = "\x1B[0;36m";
38204
- var WHITE = "\x1B[0;37m";
38205
- var CYAN_BRIGHT = "\x1B[0;96m";
38206
- var BG_BLACK_BRIGHT = "\x1B[0;100m";
38207
- var BG_RED = "\x1B[41m";
38208
- var BG_YELLOW = "\x1B[43m";
38209
- var BG_BLUE = "\x1B[0;44m";
38210
- var BG_CYAN = "\x1B[0;46m";
38211
38308
  var ansi = (text2, code) => `${code}${text2}${RESET}`;
38212
38309
  var ERASE_LINE = "\x1B[2K";
38213
38310
  var CURSOR_LEFT = "\r";
@@ -38215,6 +38312,11 @@ var CURSOR_HIDE = "\x1B[?25l";
38215
38312
  var CURSOR_SHOW = "\x1B[?25h";
38216
38313
  var CURSOR_TO_0 = "\x1B[G";
38217
38314
  var BEEP = "\x07";
38315
+ var ITALIC = "\x1B[0;3m";
38316
+ var UNDERLINE = "\x1B[0;4m";
38317
+ var ANSI_ESCAPE_REGEX = new RegExp(String.raw`\u001b\[[0-?]*[ -/]*[@-~]`, "g");
38318
+ var stripAnsi2 = (text2) => text2.replace(ANSI_ESCAPE_REGEX, "");
38319
+ var visibleLength = (text2) => stripAnsi2(text2).length;
38218
38320
 
38219
38321
  // src/cli/overview.ts
38220
38322
  var import_project_service3 = __toESM(require_dist3());
@@ -40178,9 +40280,100 @@ var computeTsConfigChanges = (current, target, lspVersion) => {
40178
40280
  if (!rootObj) {
40179
40281
  return emptyFileChangesResult();
40180
40282
  }
40283
+ const buildPluginObject = (severities) => {
40284
+ const nameProperty = ts.factory.createPropertyAssignment(
40285
+ ts.factory.createStringLiteral("name"),
40286
+ ts.factory.createStringLiteral("@effect/language-service")
40287
+ );
40288
+ return match(severities, {
40289
+ onNone: () => {
40290
+ return ts.factory.createObjectLiteralExpression([nameProperty], false);
40291
+ },
40292
+ onSome: (sevs) => {
40293
+ const severityProperties = Object.entries(sevs).map(
40294
+ ([name, severity]) => ts.factory.createPropertyAssignment(
40295
+ ts.factory.createStringLiteral(name),
40296
+ ts.factory.createStringLiteral(severity)
40297
+ )
40298
+ );
40299
+ const diagnosticSeverityProperty = ts.factory.createPropertyAssignment(
40300
+ ts.factory.createStringLiteral("diagnosticSeverity"),
40301
+ ts.factory.createObjectLiteralExpression(severityProperties, true)
40302
+ );
40303
+ return ts.factory.createObjectLiteralExpression(
40304
+ [nameProperty, diagnosticSeverityProperty],
40305
+ true
40306
+ );
40307
+ }
40308
+ });
40309
+ };
40310
+ const schemaPropertyAssignment = ts.factory.createPropertyAssignment(
40311
+ ts.factory.createStringLiteral("$schema"),
40312
+ ts.factory.createStringLiteral(TSCONFIG_SCHEMA_URL)
40313
+ );
40181
40314
  const compilerOptionsProperty = findPropertyInObject(ts, rootObj, "compilerOptions");
40182
40315
  if (!compilerOptionsProperty) {
40183
- return emptyFileChangesResult();
40316
+ if (isNone2(lspVersion)) {
40317
+ return emptyFileChangesResult();
40318
+ }
40319
+ const textChanges2 = ts.textChanges;
40320
+ const host2 = createMinimalHost(ts);
40321
+ const formatOptions2 = { indentSize: 2, tabSize: 2 };
40322
+ const formatContext2 = ts.formatting.getFormatContext(formatOptions2, host2);
40323
+ const preferences2 = {};
40324
+ const fileChanges2 = textChanges2.ChangeTracker.with(
40325
+ { host: host2, formatContext: formatContext2, preferences: preferences2 },
40326
+ (tracker) => {
40327
+ const schemaProperty = findPropertyInObject(ts, rootObj, "$schema");
40328
+ const shouldAddSchema = !schemaProperty;
40329
+ const shouldUpdateSchema = !!schemaProperty && (!ts.isStringLiteral(schemaProperty.initializer) || schemaProperty.initializer.text !== TSCONFIG_SCHEMA_URL);
40330
+ if (shouldAddSchema) {
40331
+ descriptions.push("Add $schema to tsconfig");
40332
+ } else if (shouldUpdateSchema) {
40333
+ descriptions.push("Update $schema in tsconfig");
40334
+ }
40335
+ descriptions.push("Add compilerOptions with @effect/language-service plugin");
40336
+ const compilerOptionsAssignment = ts.factory.createPropertyAssignment(
40337
+ ts.factory.createStringLiteral("compilerOptions"),
40338
+ ts.factory.createObjectLiteralExpression([
40339
+ ts.factory.createPropertyAssignment(
40340
+ ts.factory.createStringLiteral("plugins"),
40341
+ ts.factory.createArrayLiteralExpression([buildPluginObject(target.diagnosticSeverities)], true)
40342
+ )
40343
+ ], true)
40344
+ );
40345
+ const nextProperties = rootObj.properties.flatMap((property) => {
40346
+ if (schemaProperty && property === schemaProperty) {
40347
+ return [schemaPropertyAssignment];
40348
+ }
40349
+ return [property];
40350
+ });
40351
+ if (shouldAddSchema) {
40352
+ nextProperties.push(schemaPropertyAssignment);
40353
+ }
40354
+ nextProperties.push(compilerOptionsAssignment);
40355
+ tracker.replaceNode(
40356
+ current.sourceFile,
40357
+ rootObj,
40358
+ ts.factory.createObjectLiteralExpression(nextProperties, true)
40359
+ );
40360
+ }
40361
+ );
40362
+ const fileChange2 = fileChanges2.find((fc) => fc.fileName === current.path);
40363
+ const changes2 = fileChange2 ? fileChange2.textChanges : [];
40364
+ if (changes2.length === 0) {
40365
+ return { codeActions: [], messages };
40366
+ }
40367
+ return {
40368
+ codeActions: [{
40369
+ description: descriptions.join("; "),
40370
+ changes: [{
40371
+ fileName: current.path,
40372
+ textChanges: changes2
40373
+ }]
40374
+ }],
40375
+ messages
40376
+ };
40184
40377
  }
40185
40378
  if (!ts.isObjectLiteralExpression(compilerOptionsProperty.initializer)) {
40186
40379
  return emptyFileChangesResult();
@@ -40196,10 +40389,6 @@ var computeTsConfigChanges = (current, target, lspVersion) => {
40196
40389
  (tracker) => {
40197
40390
  const schemaProperty = findPropertyInObject(ts, rootObj, "$schema");
40198
40391
  const pluginsProperty = findPropertyInObject(ts, compilerOptions, "plugins");
40199
- const schemaPropertyAssignment = ts.factory.createPropertyAssignment(
40200
- ts.factory.createStringLiteral("$schema"),
40201
- ts.factory.createStringLiteral(TSCONFIG_SCHEMA_URL)
40202
- );
40203
40392
  if (isNone2(lspVersion)) {
40204
40393
  if (schemaProperty) {
40205
40394
  descriptions.push("Remove $schema from tsconfig");
@@ -40229,33 +40418,6 @@ var computeTsConfigChanges = (current, target, lspVersion) => {
40229
40418
  descriptions.push("Update $schema in tsconfig");
40230
40419
  tracker.replaceNode(current.sourceFile, schemaProperty.initializer, schemaPropertyAssignment.initializer);
40231
40420
  }
40232
- const buildPluginObject = (severities) => {
40233
- const nameProperty = ts.factory.createPropertyAssignment(
40234
- ts.factory.createStringLiteral("name"),
40235
- ts.factory.createStringLiteral("@effect/language-service")
40236
- );
40237
- return match(severities, {
40238
- onNone: () => {
40239
- return ts.factory.createObjectLiteralExpression([nameProperty], false);
40240
- },
40241
- onSome: (sevs) => {
40242
- const severityProperties = Object.entries(sevs).map(
40243
- ([name, severity]) => ts.factory.createPropertyAssignment(
40244
- ts.factory.createStringLiteral(name),
40245
- ts.factory.createStringLiteral(severity)
40246
- )
40247
- );
40248
- const diagnosticSeverityProperty = ts.factory.createPropertyAssignment(
40249
- ts.factory.createStringLiteral("diagnosticSeverity"),
40250
- ts.factory.createObjectLiteralExpression(severityProperties, true)
40251
- );
40252
- return ts.factory.createObjectLiteralExpression(
40253
- [nameProperty, diagnosticSeverityProperty],
40254
- true
40255
- );
40256
- }
40257
- });
40258
- };
40259
40421
  const pluginObject = buildPluginObject(target.diagnosticSeverities);
40260
40422
  if (!pluginsProperty) {
40261
40423
  descriptions.push("Add plugins array with @effect/language-service plugin");
@@ -40468,244 +40630,1656 @@ var FileReadError = class extends TaggedError2("FileReadError") {
40468
40630
  }
40469
40631
  };
40470
40632
 
40471
- // src/cli/setup/diagnostic-info.ts
40472
- function getAllDiagnostics() {
40473
- return diagnostics.map((diagnostic) => ({
40474
- name: diagnostic.name,
40475
- code: diagnostic.code,
40476
- defaultSeverity: diagnostic.severity,
40477
- description: diagnostic.description
40478
- }));
40479
- }
40480
- function cycleSeverity(current, direction) {
40481
- const order = ["off", "suggestion", "message", "warning", "error"];
40482
- const currentIndex = order.indexOf(current);
40483
- if (direction === "right") {
40484
- return order[(currentIndex + 1) % order.length];
40485
- } else {
40486
- return order[(currentIndex - 1 + order.length) % order.length];
40487
- }
40488
- }
40489
- var shortNames = {
40490
- off: "off",
40491
- suggestion: "sugg",
40492
- message: "info",
40493
- warning: "warn",
40494
- error: "err"
40495
- };
40496
- var MAX_SEVERITY_LENGTH = Object.values(shortNames).reduce((max2, name) => Math.max(max2, name.length), 0);
40497
- function getSeverityShortName(severity) {
40498
- return shortNames[severity] ?? "???";
40499
- }
40500
-
40501
- // src/cli/setup/diagnostic-prompt.ts
40502
- function eraseLines2(count) {
40503
- let result3 = "";
40504
- for (let i = 0; i < count; i++) {
40505
- if (i > 0) result3 += "\x1B[1A";
40506
- result3 += ERASE_LINE;
40507
- }
40508
- if (count > 0) result3 += CURSOR_LEFT;
40509
- return result3;
40510
- }
40511
- var Action2 = taggedEnum();
40512
- var NEWLINE_REGEX = /\r?\n/;
40513
- function eraseText2(text2, columns) {
40514
- if (columns === 0) {
40515
- return ERASE_LINE + CURSOR_TO_0;
40516
- }
40517
- let rows = 0;
40518
- const lines2 = text2.split(/\r?\n/);
40519
- for (const line of lines2) {
40520
- rows += 1 + Math.floor(Math.max(line.length - 1, 0) / columns);
40521
- }
40522
- return eraseLines2(rows);
40523
- }
40524
- function entriesToDisplay2(cursor, total, maxVisible) {
40525
- const max2 = maxVisible === void 0 ? total : maxVisible;
40526
- let startIndex = Math.min(total - max2, cursor - Math.floor(max2 / 2));
40527
- if (startIndex < 0) {
40528
- startIndex = 0;
40529
- }
40530
- const endIndex = Math.min(startIndex + max2, total);
40531
- return { startIndex, endIndex };
40532
- }
40533
- var defaultFigures2 = {
40534
- arrowUp: "\u2191",
40535
- arrowDown: "\u2193",
40536
- tick: "\u2714",
40537
- pointerSmall: "\u203A"
40538
- };
40539
- var figuresValue = defaultFigures2;
40540
- function getSeverityStyle(severity) {
40541
- const styles = {
40542
- off: WHITE + BG_BLACK_BRIGHT,
40543
- suggestion: WHITE + BG_CYAN,
40544
- message: WHITE + BG_BLUE,
40545
- warning: WHITE + BG_YELLOW,
40546
- error: WHITE + BG_RED
40547
- };
40548
- return styles[severity];
40549
- }
40550
- function renderOutput(leadingSymbol, trailingSymbol, options) {
40551
- const annotateLine2 = (line) => ansi(line, BOLD);
40552
- const prefix = leadingSymbol + " ";
40553
- return match3(options.message.split(NEWLINE_REGEX), {
40554
- onEmpty: () => `${prefix}${trailingSymbol}`,
40555
- onNonEmpty: (promptLines) => {
40556
- const lines2 = map4(promptLines, (line) => annotateLine2(line));
40557
- return `${prefix}${lines2.join("\n ")} ${trailingSymbol} `;
40558
- }
40559
- });
40560
- }
40561
- function renderDiagnostics(state, options, figs, columns) {
40562
- const diagnostics3 = options.diagnostics;
40563
- const toDisplay = entriesToDisplay2(state.index, diagnostics3.length, options.maxPerPage);
40564
- const documents = [];
40565
- for (let index = toDisplay.startIndex; index < toDisplay.endIndex; index++) {
40566
- const diagnostic = diagnostics3[index];
40567
- const isHighlighted = state.index === index;
40568
- const currentSeverity = state.severities[diagnostic.name] ?? diagnostic.defaultSeverity;
40569
- const hasChanged = currentSeverity !== diagnostic.defaultSeverity;
40570
- let prefix = " ";
40571
- if (index === toDisplay.startIndex && toDisplay.startIndex > 0) {
40572
- prefix = figs.arrowUp;
40573
- } else if (index === toDisplay.endIndex - 1 && toDisplay.endIndex < diagnostics3.length) {
40574
- prefix = figs.arrowDown;
40575
- }
40576
- const shortName = getSeverityShortName(currentSeverity);
40577
- const paddedSeverity = shortName.padEnd(MAX_SEVERITY_LENGTH, " ");
40578
- const severityStr = ansi(` ${paddedSeverity} `, getSeverityStyle(currentSeverity));
40579
- const nameText = hasChanged ? `${diagnostic.name}*` : diagnostic.name;
40580
- const nameStr = isHighlighted ? ansi(nameText, CYAN_BRIGHT) : nameText;
40581
- const mainLine = `${prefix} ${severityStr} ${nameStr}`;
40582
- if (isHighlighted && diagnostic.description) {
40583
- const indentWidth = 1 + 1 + (MAX_SEVERITY_LENGTH + 2) + 1;
40584
- const indent = " ".repeat(indentWidth);
40585
- const availableWidth = columns - indentWidth;
40586
- const truncatedDescription = availableWidth > 0 && diagnostic.description.length > availableWidth ? diagnostic.description.substring(0, availableWidth - 1) + "\u2026" : diagnostic.description;
40587
- const descriptionStr = ansi(indent + truncatedDescription, DIM);
40588
- documents.push(mainLine + "\n" + descriptionStr);
40589
- } else {
40590
- documents.push(mainLine);
40591
- }
40592
- }
40593
- return documents.join("\n");
40594
- }
40595
- function renderNextFrame(state, options) {
40596
- return gen2(function* () {
40597
- const terminal = yield* Terminal;
40598
- const columns = yield* terminal.columns;
40599
- const figs = figuresValue;
40600
- const diagnosticsStr = renderDiagnostics(state, options, figs, columns);
40601
- const leadingSymbol = ansi("?", CYAN_BRIGHT);
40602
- const trailingSymbol = figs.pointerSmall;
40603
- const promptMsg = renderOutput(leadingSymbol, trailingSymbol, options);
40604
- const helpText = ansi(
40605
- "Use \u2191/\u2193 to navigate, \u2190/\u2192 to change severity, Enter to finish",
40606
- DIM
40607
- );
40608
- return CURSOR_HIDE + promptMsg + "\n" + helpText + "\n" + diagnosticsStr;
40609
- });
40610
- }
40611
- function renderSubmission(state, options) {
40612
- return gen2(function* () {
40613
- const figs = figuresValue;
40614
- const changedCount = Object.entries(state.severities).filter(([name, severity]) => {
40615
- const diagnostic = options.diagnostics.find((d) => d.name === name);
40616
- return diagnostic && severity !== diagnostic.defaultSeverity;
40617
- }).length;
40618
- const result3 = ansi(
40619
- `${changedCount} diagnostic${changedCount === 1 ? "" : "s"} configured`,
40620
- WHITE
40621
- );
40622
- const leadingSymbol = ansi(figs.tick, GREEN);
40623
- const trailingSymbol = "";
40624
- const promptMsg = renderOutput(leadingSymbol, trailingSymbol, options);
40625
- return promptMsg + " " + result3 + "\n";
40626
- });
40627
- }
40628
- function processCursorUp(state, totalCount) {
40629
- const newIndex = state.index === 0 ? totalCount - 1 : state.index - 1;
40630
- return succeed6(Action2.NextFrame({ state: { ...state, index: newIndex } }));
40631
- }
40632
- function processCursorDown(state, totalCount) {
40633
- const newIndex = (state.index + 1) % totalCount;
40634
- return succeed6(Action2.NextFrame({ state: { ...state, index: newIndex } }));
40635
- }
40636
- function processSeverityChange(state, options, direction) {
40637
- const diagnostic = options.diagnostics[state.index];
40638
- const currentSeverity = state.severities[diagnostic.name] ?? diagnostic.defaultSeverity;
40639
- const newSeverity = cycleSeverity(currentSeverity, direction);
40640
- return succeed6(Action2.NextFrame({
40641
- state: {
40642
- ...state,
40643
- severities: { ...state.severities, [diagnostic.name]: newSeverity }
40633
+ // src/metadata.json
40634
+ var metadata_default = {
40635
+ groups: [
40636
+ {
40637
+ id: "correctness",
40638
+ name: "Correctness",
40639
+ description: "Wrong, unsafe, or structurally invalid code patterns."
40640
+ },
40641
+ {
40642
+ id: "antipattern",
40643
+ name: "Anti-pattern",
40644
+ description: "Discouraged patterns that often lead to bugs or confusing behavior."
40645
+ },
40646
+ {
40647
+ id: "effectNative",
40648
+ name: "Effect-native",
40649
+ description: "Prefer Effect-native APIs and abstractions when available."
40650
+ },
40651
+ {
40652
+ id: "style",
40653
+ name: "Style",
40654
+ description: "Cleanup, consistency, and idiomatic Effect code."
40644
40655
  }
40645
- }));
40646
- }
40647
- function handleProcess(options) {
40648
- return (input, state) => {
40649
- const totalCount = options.diagnostics.length;
40650
- switch (input.key.name) {
40651
- case "k":
40652
- case "up": {
40653
- return processCursorUp(state, totalCount);
40656
+ ],
40657
+ rules: [
40658
+ {
40659
+ name: "anyUnknownInErrorContext",
40660
+ group: "correctness",
40661
+ description: "Detects 'any' or 'unknown' types in Effect error or requirements channels",
40662
+ defaultSeverity: "off",
40663
+ fixable: false,
40664
+ supportedEffect: [
40665
+ "v3",
40666
+ "v4"
40667
+ ],
40668
+ preview: {
40669
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.gen(function*() {\n yield* Effect.services<unknown>()\n return yield* Effect.fail<any>("boom")\n})\n',
40670
+ diagnostics: [
40671
+ {
40672
+ start: 46,
40673
+ end: 53,
40674
+ text: "This has unknown in the requirements channel and any in the error channel which is not recommended.\nOnly service identifiers should appear in the requirements channel.\nHaving an unknown or any error type is not useful. Consider instead using specific error types baked by Data.TaggedError for example. effect(anyUnknownInErrorContext)"
40675
+ },
40676
+ {
40677
+ start: 90,
40678
+ end: 116,
40679
+ text: "This has unknown in the requirements channel which is not recommended.\nOnly service identifiers should appear in the requirements channel. effect(anyUnknownInErrorContext)"
40680
+ },
40681
+ {
40682
+ start: 133,
40683
+ end: 157,
40684
+ text: "This has any in the error channel which is not recommended.\nHaving an unknown or any error type is not useful. Consider instead using specific error types baked by Data.TaggedError for example. effect(anyUnknownInErrorContext)"
40685
+ }
40686
+ ]
40654
40687
  }
40655
- case "j":
40656
- case "down": {
40657
- return processCursorDown(state, totalCount);
40688
+ },
40689
+ {
40690
+ name: "classSelfMismatch",
40691
+ group: "correctness",
40692
+ description: "Ensures Self type parameter matches the class name in Service/Tag/Schema classes",
40693
+ defaultSeverity: "error",
40694
+ fixable: true,
40695
+ supportedEffect: [
40696
+ "v3",
40697
+ "v4"
40698
+ ],
40699
+ preview: {
40700
+ sourceText: 'import * as Effect from "effect/Effect"\n\ninterface ServiceShape { value: number }\n\nexport class InvalidContextTag\n extends Effect.Tag("ValidContextTag")<ValidContextTag, ServiceShape>() {}\n\ndeclare class ValidContextTag {}\n',
40701
+ diagnostics: [
40702
+ {
40703
+ start: 154,
40704
+ end: 169,
40705
+ text: "Self type parameter should be 'InvalidContextTag' effect(classSelfMismatch)"
40706
+ }
40707
+ ]
40658
40708
  }
40659
- case "left": {
40660
- return processSeverityChange(state, options, "left");
40709
+ },
40710
+ {
40711
+ name: "duplicatePackage",
40712
+ group: "correctness",
40713
+ description: "Detects when multiple versions of the same Effect package are loaded",
40714
+ defaultSeverity: "warning",
40715
+ fixable: false,
40716
+ supportedEffect: [
40717
+ "v3",
40718
+ "v4"
40719
+ ],
40720
+ preview: {
40721
+ sourceText: 'import * as Effect from "effect/Effect"\n\n// This preview only reports when the workspace resolves duplicated\n// Effect package versions.\nexport const preview = Effect.succeed(true)\n',
40722
+ diagnostics: []
40661
40723
  }
40662
- case "right": {
40663
- return processSeverityChange(state, options, "right");
40724
+ },
40725
+ {
40726
+ name: "floatingEffect",
40727
+ group: "correctness",
40728
+ description: "Ensures Effects are yielded or assigned to variables, not left floating",
40729
+ defaultSeverity: "error",
40730
+ fixable: false,
40731
+ supportedEffect: [
40732
+ "v3",
40733
+ "v4"
40734
+ ],
40735
+ preview: {
40736
+ sourceText: 'import * as Effect from "effect/Effect"\n\nEffect.log("forgotten")\n',
40737
+ diagnostics: [
40738
+ {
40739
+ start: 41,
40740
+ end: 64,
40741
+ text: "Effect must be yielded or assigned to a variable. effect(floatingEffect)"
40742
+ }
40743
+ ]
40664
40744
  }
40665
- case "enter":
40666
- case "return": {
40667
- return succeed6(Action2.Submit({ value: state.severities }));
40745
+ },
40746
+ {
40747
+ name: "genericEffectServices",
40748
+ group: "correctness",
40749
+ description: "Prevents services with type parameters that cannot be discriminated at runtime",
40750
+ defaultSeverity: "warning",
40751
+ fixable: false,
40752
+ supportedEffect: [
40753
+ "v3",
40754
+ "v4"
40755
+ ],
40756
+ preview: {
40757
+ sourceText: 'import { Effect } from "effect"\n\nexport class Preview<_A> extends Effect.Service<Preview<any>>()("Preview", {\n succeed: {}\n}) {}\n',
40758
+ diagnostics: [
40759
+ {
40760
+ start: 46,
40761
+ end: 53,
40762
+ text: "Effect Services with type parameters are not supported because they cannot be properly discriminated at runtime, which may cause unexpected behavior. effect(genericEffectServices)"
40763
+ }
40764
+ ]
40668
40765
  }
40669
- default: {
40670
- return succeed6(Action2.Beep());
40766
+ },
40767
+ {
40768
+ name: "missingEffectContext",
40769
+ group: "correctness",
40770
+ description: "Reports missing service requirements in Effect context channel",
40771
+ defaultSeverity: "error",
40772
+ fixable: false,
40773
+ supportedEffect: [
40774
+ "v3",
40775
+ "v4"
40776
+ ],
40777
+ preview: {
40778
+ sourceText: 'import { Effect, ServiceMap } from "effect"\n\nclass Db extends ServiceMap.Service<Db>()("Db", { make: Effect.succeed({}) }) {}\n\n// @ts-expect-error\nexport const preview: Effect.Effect<void> = Db.asEffect().pipe(Effect.asVoid)\n',
40779
+ diagnostics: [
40780
+ {
40781
+ start: 160,
40782
+ end: 167,
40783
+ text: "Missing 'Db' in the expected Effect context. effect(missingEffectContext)"
40784
+ }
40785
+ ]
40671
40786
  }
40672
- }
40673
- };
40674
- }
40675
- function handleClear(options) {
40676
- return gen2(function* () {
40677
- const terminal = yield* Terminal;
40678
- const columns = yield* terminal.columns;
40679
- const clearPrompt = ERASE_LINE + CURSOR_LEFT;
40680
- const visibleCount = Math.min(options.diagnostics.length, options.maxPerPage);
40681
- const text2 = "\n".repeat(visibleCount + 2) + options.message;
40682
- const clearOutput = eraseText2(text2, columns);
40683
- return clearOutput + clearPrompt;
40684
- });
40685
- }
40686
- function handleRender(options) {
40687
- return (state, action2) => {
40688
- return Action2.$match(action2, {
40689
- Beep: () => succeed6(BEEP),
40690
- NextFrame: ({ state: state2 }) => renderNextFrame(state2, options),
40691
- Submit: () => renderSubmission(state, options)
40692
- });
40693
- };
40694
- }
40695
- function createDiagnosticPrompt(diagnostics3, initialSeverities) {
40696
- const options = {
40697
- message: "Configure Diagnostic Severities",
40698
- diagnostics: diagnostics3,
40699
- maxPerPage: 10
40700
- };
40701
- const initialState = {
40702
- index: 0,
40703
- severities: initialSeverities
40704
- };
40705
- return custom(initialState, {
40706
- render: handleRender(options),
40707
- process: handleProcess(options),
40708
- clear: () => handleClear(options)
40787
+ },
40788
+ {
40789
+ name: "missingEffectError",
40790
+ group: "correctness",
40791
+ description: "Reports missing error types in Effect error channel",
40792
+ defaultSeverity: "error",
40793
+ fixable: true,
40794
+ supportedEffect: [
40795
+ "v3",
40796
+ "v4"
40797
+ ],
40798
+ preview: {
40799
+ sourceText: 'import type * as Effect from "effect/Effect"\nimport { Data } from "effect"\n\nclass Boom extends Data.TaggedError("Boom")<{}> {}\ndeclare const effect: Effect.Effect<number, Boom>\n\n// @ts-expect-error\nexport const preview: () => Effect.Effect<number> = () => effect\n',
40800
+ diagnostics: [
40801
+ {
40802
+ start: 256,
40803
+ end: 262,
40804
+ text: "Missing 'Boom' in the expected Effect errors. effect(missingEffectError)"
40805
+ }
40806
+ ]
40807
+ }
40808
+ },
40809
+ {
40810
+ name: "missingLayerContext",
40811
+ group: "correctness",
40812
+ description: "Reports missing service requirements in Layer context channel",
40813
+ defaultSeverity: "error",
40814
+ fixable: false,
40815
+ supportedEffect: [
40816
+ "v3",
40817
+ "v4"
40818
+ ],
40819
+ preview: {
40820
+ sourceText: 'import { Effect, Layer, ServiceMap } from "effect"\n\nclass A extends ServiceMap.Service<A>()("A", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\ndeclare const layer: Layer.Layer<A, never, A>\n// @ts-expect-error\nexport const preview: Layer.Layer<A> = layer\n',
40821
+ diagnostics: [
40822
+ {
40823
+ start: 259,
40824
+ end: 266,
40825
+ text: "Missing 'A' in the expected Layer context. effect(missingLayerContext)"
40826
+ }
40827
+ ]
40828
+ }
40829
+ },
40830
+ {
40831
+ name: "missingReturnYieldStar",
40832
+ group: "correctness",
40833
+ description: "Suggests using 'return yield*' for Effects with never success for better type narrowing",
40834
+ defaultSeverity: "error",
40835
+ fixable: true,
40836
+ supportedEffect: [
40837
+ "v3",
40838
+ "v4"
40839
+ ],
40840
+ preview: {
40841
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n yield* Effect.log("before")\n yield* Effect.fail("boom")\n})\n',
40842
+ diagnostics: [
40843
+ {
40844
+ start: 121,
40845
+ end: 147,
40846
+ text: "It is recommended to use return yield* for Effects that never succeed to signal a definitive exit point for type narrowing and tooling support. effect(missingReturnYieldStar)"
40847
+ }
40848
+ ]
40849
+ }
40850
+ },
40851
+ {
40852
+ name: "missingStarInYieldEffectGen",
40853
+ group: "correctness",
40854
+ description: "Enforces using 'yield*' instead of 'yield' when yielding Effects in generators",
40855
+ defaultSeverity: "error",
40856
+ fixable: true,
40857
+ supportedEffect: [
40858
+ "v3",
40859
+ "v4"
40860
+ ],
40861
+ preview: {
40862
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n const value = yield Effect.succeed(1)\n return value\n})\n',
40863
+ diagnostics: [
40864
+ {
40865
+ start: 75,
40866
+ end: 83,
40867
+ text: "Seems like you used yield instead of yield* inside this Effect.gen. effect(missingStarInYieldEffectGen)"
40868
+ },
40869
+ {
40870
+ start: 105,
40871
+ end: 128,
40872
+ text: "When yielding Effects inside Effect.gen, you should use yield* instead of yield. effect(missingStarInYieldEffectGen)"
40873
+ }
40874
+ ]
40875
+ }
40876
+ },
40877
+ {
40878
+ name: "nonObjectEffectServiceType",
40879
+ group: "correctness",
40880
+ description: "Ensures Effect.Service types are objects, not primitives",
40881
+ defaultSeverity: "error",
40882
+ fixable: false,
40883
+ supportedEffect: [
40884
+ "v3"
40885
+ ],
40886
+ preview: {
40887
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport class BadService extends Effect.Service<BadService>()("BadService", {\n // @ts-expect-error\n succeed: "hello" as const\n}) {}\n',
40888
+ diagnostics: [
40889
+ {
40890
+ start: 142,
40891
+ end: 149,
40892
+ text: "Effect.Service requires the service type to be an object {} and not a primitive type. \nConsider wrapping the value in an object, or manually using Context.Tag or Effect.Tag if you want to use a primitive instead. effect(nonObjectEffectServiceType)"
40893
+ }
40894
+ ]
40895
+ }
40896
+ },
40897
+ {
40898
+ name: "outdatedApi",
40899
+ group: "correctness",
40900
+ description: "Detects usage of APIs that have been removed or renamed in Effect v4",
40901
+ defaultSeverity: "warning",
40902
+ fixable: false,
40903
+ supportedEffect: [
40904
+ "v4"
40905
+ ],
40906
+ preview: {
40907
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.gen(function*() {\n // @ts-expect-error\n return yield* Effect.runtime()\n})\n',
40908
+ diagnostics: [
40909
+ {
40910
+ start: 0,
40911
+ end: 0,
40912
+ text: "This project targets Effect v4, but is using Effect v3 APIs. To find the correct API to use, clone and consult the github.com/effect-ts/effect-smol repository for the corresponding v4 replacement. effect(outdatedApi)"
40913
+ },
40914
+ {
40915
+ start: 126,
40916
+ end: 133,
40917
+ text: "runtime is an Effect v3 API, but the project is using Effect v4. effect(outdatedApi)"
40918
+ }
40919
+ ]
40920
+ }
40921
+ },
40922
+ {
40923
+ name: "outdatedEffectCodegen",
40924
+ group: "correctness",
40925
+ description: "Detects when generated code is outdated and needs to be regenerated",
40926
+ defaultSeverity: "warning",
40927
+ fixable: true,
40928
+ supportedEffect: [
40929
+ "v3",
40930
+ "v4"
40931
+ ],
40932
+ preview: {
40933
+ sourceText: 'import * as Effect from "effect/Effect"\n\n// @effect-codegens accessors:stale-preview\nexport class Preview extends Effect.Service<Preview>()("Preview", {\n accessors: true,\n effect: Effect.succeed({ value: Effect.succeed(1) })\n}) {}\n',
40934
+ diagnostics: [
40935
+ {
40936
+ start: 61,
40937
+ end: 76,
40938
+ text: "Codegen accessors result is outdated effect(outdatedEffectCodegen)"
40939
+ }
40940
+ ]
40941
+ }
40942
+ },
40943
+ {
40944
+ name: "overriddenSchemaConstructor",
40945
+ group: "correctness",
40946
+ description: "Prevents overriding constructors in Schema classes which breaks decoding behavior",
40947
+ defaultSeverity: "error",
40948
+ fixable: true,
40949
+ supportedEffect: [
40950
+ "v3",
40951
+ "v4"
40952
+ ],
40953
+ preview: {
40954
+ sourceText: 'import * as Schema from "effect/Schema"\n\nexport class User extends Schema.Class<User>("User")({ name: Schema.String }) {\n constructor(readonly input: { name: string }) { super(input) }\n}\n',
40955
+ diagnostics: [
40956
+ {
40957
+ start: 123,
40958
+ end: 185,
40959
+ text: "Classes extending Schema must not override the constructor; this is because it silently breaks the schema decoding behaviour. If that's needed, we recommend instead to use a static 'new' method that constructs the instance. effect(overriddenSchemaConstructor)"
40960
+ }
40961
+ ]
40962
+ }
40963
+ },
40964
+ {
40965
+ name: "unsupportedServiceAccessors",
40966
+ group: "correctness",
40967
+ description: "Warns about service accessors that need codegen due to generic/complex signatures",
40968
+ defaultSeverity: "warning",
40969
+ fixable: true,
40970
+ supportedEffect: [
40971
+ "v3",
40972
+ "v4"
40973
+ ],
40974
+ preview: {
40975
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport class Preview extends Effect.Service<Preview>()("Preview", {\n accessors: true,\n effect: Effect.succeed({ get: <A>(value: A) => Effect.succeed(value) })\n}) {}\n',
40976
+ diagnostics: [
40977
+ {
40978
+ start: 54,
40979
+ end: 61,
40980
+ text: "Even if accessors are enabled, accessors for 'get' won't be available because the signature have generic type parameters or multiple call signatures. effect(unsupportedServiceAccessors)"
40981
+ }
40982
+ ]
40983
+ }
40984
+ },
40985
+ {
40986
+ name: "catchUnfailableEffect",
40987
+ group: "antipattern",
40988
+ description: "Warns when using error handling on Effects that never fail (error type is 'never')",
40989
+ defaultSeverity: "suggestion",
40990
+ fixable: false,
40991
+ supportedEffect: [
40992
+ "v3",
40993
+ "v4"
40994
+ ],
40995
+ preview: {
40996
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.succeed(1).pipe(\n Effect.catch(() => Effect.succeed(0))\n)\n',
40997
+ diagnostics: [
40998
+ {
40999
+ start: 82,
41000
+ end: 94,
41001
+ text: "Looks like the previous effect never fails, so probably this error handling will never be triggered. effect(catchUnfailableEffect)"
41002
+ }
41003
+ ]
41004
+ }
41005
+ },
41006
+ {
41007
+ name: "effectFnIife",
41008
+ group: "antipattern",
41009
+ description: "Effect.fn or Effect.fnUntraced is called as an IIFE (Immediately Invoked Function Expression). Use Effect.gen instead.",
41010
+ defaultSeverity: "warning",
41011
+ fixable: true,
41012
+ supportedEffect: [
41013
+ "v3",
41014
+ "v4"
41015
+ ],
41016
+ preview: {
41017
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.fn("preview")(function*() {\n return yield* Effect.succeed(1)\n})()\n',
41018
+ diagnostics: [
41019
+ {
41020
+ start: 64,
41021
+ end: 137,
41022
+ text: `Effect.fn returns a reusable function that can take arguments, but here it's called immediately. Use Effect.gen instead with Effect.withSpan("preview") piped in the end to mantain tracing spans. effect(effectFnIife)`
41023
+ }
41024
+ ]
41025
+ }
41026
+ },
41027
+ {
41028
+ name: "effectGenUsesAdapter",
41029
+ group: "antipattern",
41030
+ description: "Warns when using the deprecated adapter parameter in Effect.gen",
41031
+ defaultSeverity: "warning",
41032
+ fixable: false,
41033
+ supportedEffect: [
41034
+ "v3",
41035
+ "v4"
41036
+ ],
41037
+ preview: {
41038
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*(_) {\n return yield* Effect.succeed(1)\n})\n',
41039
+ diagnostics: [
41040
+ {
41041
+ start: 85,
41042
+ end: 86,
41043
+ text: "The adapter of Effect.gen is not required anymore, it is now just an alias of pipe. effect(effectGenUsesAdapter)"
41044
+ }
41045
+ ]
41046
+ }
41047
+ },
41048
+ {
41049
+ name: "effectInFailure",
41050
+ group: "antipattern",
41051
+ description: "Warns when an Effect is used inside an Effect failure channel",
41052
+ defaultSeverity: "warning",
41053
+ fixable: false,
41054
+ supportedEffect: [
41055
+ "v3",
41056
+ "v4"
41057
+ ],
41058
+ preview: {
41059
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.try({\n try: () => JSON.parse("{"),\n catch: (error) => Effect.logError(error)\n})\n',
41060
+ diagnostics: [
41061
+ {
41062
+ start: 46,
41063
+ end: 53,
41064
+ text: "The error channel contains an Effect (Effect<void, never, never>). Putting Effect computations in the failure channel is not intended; keep only failure types there. effect(effectInFailure)"
41065
+ },
41066
+ {
41067
+ start: 56,
41068
+ end: 144,
41069
+ text: "The error channel contains an Effect (Effect<void, never, never>). Putting Effect computations in the failure channel is not intended; keep only failure types there. effect(effectInFailure)"
41070
+ }
41071
+ ]
41072
+ }
41073
+ },
41074
+ {
41075
+ name: "effectInVoidSuccess",
41076
+ group: "antipattern",
41077
+ description: "Detects nested Effects in void success channels that may cause unexecuted effects",
41078
+ defaultSeverity: "warning",
41079
+ fixable: false,
41080
+ supportedEffect: [
41081
+ "v3",
41082
+ "v4"
41083
+ ],
41084
+ preview: {
41085
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview: Effect.Effect<void> = Effect.succeed(\n Effect.log("nested")\n)\n',
41086
+ diagnostics: [
41087
+ {
41088
+ start: 54,
41089
+ end: 61,
41090
+ text: "There is a nested 'Effect<void, never, never>' in the 'void' success channel, beware that this could lead to nested Effect<Effect<...>> that won't be executed. effect(effectInVoidSuccess)"
41091
+ }
41092
+ ]
41093
+ }
41094
+ },
41095
+ {
41096
+ name: "globalErrorInEffectCatch",
41097
+ group: "antipattern",
41098
+ description: "Warns when catch callbacks return global Error type instead of typed errors",
41099
+ defaultSeverity: "warning",
41100
+ fixable: false,
41101
+ supportedEffect: [
41102
+ "v3",
41103
+ "v4"
41104
+ ],
41105
+ preview: {
41106
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.tryPromise({\n try: async () => 1,\n catch: (error) => new Error(String(error))\n})\n',
41107
+ diagnostics: [
41108
+ {
41109
+ start: 56,
41110
+ end: 73,
41111
+ text: "The 'catch' callback in Effect.tryPromise returns global 'Error', which loses type safety as untagged errors merge together. Consider using a tagged error and optionally wrapping the original in a 'cause' property. effect(globalErrorInEffectCatch)"
41112
+ }
41113
+ ]
41114
+ }
41115
+ },
41116
+ {
41117
+ name: "globalErrorInEffectFailure",
41118
+ group: "antipattern",
41119
+ description: "Warns when the global Error type is used in an Effect failure channel",
41120
+ defaultSeverity: "warning",
41121
+ fixable: false,
41122
+ supportedEffect: [
41123
+ "v3",
41124
+ "v4"
41125
+ ],
41126
+ preview: {
41127
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.fail(new Error("boom"))\n',
41128
+ diagnostics: [
41129
+ {
41130
+ start: 68,
41131
+ end: 85,
41132
+ text: "Global 'Error' loses type safety as untagged errors merge together in the Effect failure channel. Consider using a tagged error and optionally wrapping the original in a 'cause' property. effect(globalErrorInEffectFailure)"
41133
+ }
41134
+ ]
41135
+ }
41136
+ },
41137
+ {
41138
+ name: "layerMergeAllWithDependencies",
41139
+ group: "antipattern",
41140
+ description: "Detects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires",
41141
+ defaultSeverity: "warning",
41142
+ fixable: true,
41143
+ supportedEffect: [
41144
+ "v3",
41145
+ "v4"
41146
+ ],
41147
+ preview: {
41148
+ sourceText: 'import { Effect, Layer, ServiceMap } from "effect"\n\nclass A extends ServiceMap.Service<A>()("A", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\nclass B extends ServiceMap.Service<B>()("B", { make: Effect.as(A.asEffect(), {}) }) {\n static Default = Layer.effect(this, this.make)\n}\nexport const preview = Layer.mergeAll(A.Default, B.Default)\n',
41149
+ diagnostics: [
41150
+ {
41151
+ start: 355,
41152
+ end: 364,
41153
+ text: "This layer provides A which is required by another layer in the same Layer.mergeAll call. Layer.mergeAll creates layers in parallel, so dependencies between layers will not be satisfied. Consider moving this layer into a Layer.provideMerge after the Layer.mergeAll. effect(layerMergeAllWithDependencies)"
41154
+ }
41155
+ ]
41156
+ }
41157
+ },
41158
+ {
41159
+ name: "leakingRequirements",
41160
+ group: "antipattern",
41161
+ description: "Detects implementation services leaked in service methods",
41162
+ defaultSeverity: "suggestion",
41163
+ fixable: false,
41164
+ supportedEffect: [
41165
+ "v3",
41166
+ "v4"
41167
+ ],
41168
+ preview: {
41169
+ sourceText: 'import type { Effect } from "effect"\nimport { ServiceMap } from "effect"\n\nclass FileSystem extends ServiceMap.Service<FileSystem, {\n write: (s: string) => Effect.Effect<void>\n}>()("FileSystem") {}\n\nexport class Cache extends ServiceMap.Service<Cache, {\n read: Effect.Effect<string, never, FileSystem>\n save: () => Effect.Effect<void, never, FileSystem>\n}>()("Cache") {}\n',
41170
+ diagnostics: [
41171
+ {
41172
+ start: 212,
41173
+ end: 217,
41174
+ text: "Methods of this Service require `FileSystem` from every caller.\n\nThis leaks implementation details into the service's public type \u2014 callers shouldn't need to know *how* the service works internally, only *what* it provides.\n\nResolve these dependencies at Layer creation and provide them to each method, so the service's type reflects its purpose, not its implementation.\n\nTo suppress this diagnostic for specific dependency types that are intentionally passed through (e.g., HttpServerRequest), add `@effect-leakable-service` JSDoc to their interface declarations (e.g., the `FileSystem` interface), not to this service.\n\nMore info and examples at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage effect(leakingRequirements)"
41175
+ }
41176
+ ]
41177
+ }
41178
+ },
41179
+ {
41180
+ name: "multipleEffectProvide",
41181
+ group: "antipattern",
41182
+ description: "Warns against chaining Effect.provide calls which can cause service lifecycle issues",
41183
+ defaultSeverity: "warning",
41184
+ fixable: true,
41185
+ supportedEffect: [
41186
+ "v3",
41187
+ "v4"
41188
+ ],
41189
+ preview: {
41190
+ sourceText: 'import { Effect, Layer, ServiceMap } from "effect"\n\nclass A extends ServiceMap.Service<A>()("A", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\nclass B extends ServiceMap.Service<B>()("B", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\nexport const preview = Effect.void.pipe(Effect.provide(A.Default), Effect.provide(B.Default))\n',
41191
+ diagnostics: [
41192
+ {
41193
+ start: 348,
41194
+ end: 373,
41195
+ text: "Avoid chaining Effect.provide calls, as this can lead to service lifecycle issues. Instead, merge layers and provide them in a single call. effect(multipleEffectProvide)"
41196
+ }
41197
+ ]
41198
+ }
41199
+ },
41200
+ {
41201
+ name: "returnEffectInGen",
41202
+ group: "antipattern",
41203
+ description: "Warns when returning an Effect in a generator causes nested Effect<Effect<...>>",
41204
+ defaultSeverity: "suggestion",
41205
+ fixable: true,
41206
+ supportedEffect: [
41207
+ "v3",
41208
+ "v4"
41209
+ ],
41210
+ preview: {
41211
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n return Effect.succeed(1)\n})\n',
41212
+ diagnostics: [
41213
+ {
41214
+ start: 91,
41215
+ end: 115,
41216
+ text: "You are returning an Effect-able type inside a generator function, and will result in nested Effect<Effect<...>>.\nMaybe you wanted to return yield* instead?\nNested Effect-able types may be intended if you plan to later manually flatten or unwrap this Effect, if so you can safely disable this diagnostic for this line through quickfixes. effect(returnEffectInGen)"
41217
+ }
41218
+ ]
41219
+ }
41220
+ },
41221
+ {
41222
+ name: "runEffectInsideEffect",
41223
+ group: "antipattern",
41224
+ description: "Suggests using Runtime methods instead of Effect.run* inside Effect contexts",
41225
+ defaultSeverity: "suggestion",
41226
+ fixable: true,
41227
+ supportedEffect: [
41228
+ "v3"
41229
+ ],
41230
+ preview: {
41231
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.gen(function*() {\n const run = () => Effect.runSync(Effect.succeed(1))\n return run()\n})\n',
41232
+ diagnostics: [
41233
+ {
41234
+ start: 101,
41235
+ end: 115,
41236
+ text: "Using Effect.runSync inside an Effect is not recommended. The same runtime should generally be used instead to run child effects.\nConsider extracting the Runtime by using for example Effect.runtime and then use Runtime.runSync with the extracted runtime instead. effect(runEffectInsideEffect)"
41237
+ }
41238
+ ]
41239
+ }
41240
+ },
41241
+ {
41242
+ name: "schemaSyncInEffect",
41243
+ group: "antipattern",
41244
+ description: "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
41245
+ defaultSeverity: "suggestion",
41246
+ fixable: false,
41247
+ supportedEffect: [
41248
+ "v3"
41249
+ ],
41250
+ preview: {
41251
+ sourceText: 'import * as Effect from "effect/Effect"\nimport * as Schema from "effect/Schema"\n\nconst Person = Schema.Struct({ name: Schema.String, age: Schema.Number })\n\nexport const preview = Effect.gen(function*() {\n const input = yield* Effect.succeed({ name: "Ada", age: 1 })\n return Schema.decodeSync(Person)(input)\n})\n',
41252
+ diagnostics: [
41253
+ {
41254
+ start: 276,
41255
+ end: 293,
41256
+ text: "Using Schema.decodeSync inside an Effect generator is not recommended. Use Schema.decode instead to get properly typed error channel. effect(schemaSyncInEffect)"
41257
+ }
41258
+ ]
41259
+ }
41260
+ },
41261
+ {
41262
+ name: "scopeInLayerEffect",
41263
+ group: "antipattern",
41264
+ description: "Suggests using Layer.scoped instead of Layer.effect when Scope is in requirements",
41265
+ defaultSeverity: "warning",
41266
+ fixable: true,
41267
+ supportedEffect: [
41268
+ "v3"
41269
+ ],
41270
+ preview: {
41271
+ sourceText: 'import * as Context from "effect/Context"\nimport * as Effect from "effect/Effect"\nimport * as Layer from "effect/Layer"\n\nclass Cache extends Context.Tag("Cache")<Cache, { ok: true }>() {}\nexport const preview = Layer.effect(Cache, Effect.gen(function*() {\n yield* Effect.addFinalizer(() => Effect.void)\n return { ok: true as const }\n}))\n',
41272
+ diagnostics: [
41273
+ {
41274
+ start: 211,
41275
+ end: 338,
41276
+ text: 'Seems like you are constructing a layer with a scope in the requirements.\nConsider using "scoped" instead to get rid of the scope in the requirements. effect(scopeInLayerEffect)'
41277
+ }
41278
+ ]
41279
+ }
41280
+ },
41281
+ {
41282
+ name: "strictEffectProvide",
41283
+ group: "antipattern",
41284
+ description: "Warns when using Effect.provide with layers outside of application entry points",
41285
+ defaultSeverity: "off",
41286
+ fixable: false,
41287
+ supportedEffect: [
41288
+ "v3",
41289
+ "v4"
41290
+ ],
41291
+ preview: {
41292
+ sourceText: 'import { Effect, Layer, ServiceMap } from "effect"\n\nclass Config extends ServiceMap.Service<Config>()("Config", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\nexport const preview = Effect.void.pipe(Effect.provide(Config.Default))\n',
41293
+ diagnostics: [
41294
+ {
41295
+ start: 235,
41296
+ end: 265,
41297
+ text: "Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide)"
41298
+ }
41299
+ ]
41300
+ }
41301
+ },
41302
+ {
41303
+ name: "tryCatchInEffectGen",
41304
+ group: "antipattern",
41305
+ description: "Discourages try/catch in Effect generators in favor of Effect error handling",
41306
+ defaultSeverity: "suggestion",
41307
+ fixable: false,
41308
+ supportedEffect: [
41309
+ "v3",
41310
+ "v4"
41311
+ ],
41312
+ preview: {
41313
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n try { return yield* Effect.succeed(1) }\n catch { return 0 }\n})\n',
41314
+ diagnostics: [
41315
+ {
41316
+ start: 91,
41317
+ end: 151,
41318
+ text: "Avoid using try/catch inside Effect generators. Use Effect's error handling mechanisms instead (e.g. Effect.try, Effect.tryPromise, Effect.catch, Effect.catchTag). effect(tryCatchInEffectGen)"
41319
+ }
41320
+ ]
41321
+ }
41322
+ },
41323
+ {
41324
+ name: "unknownInEffectCatch",
41325
+ group: "antipattern",
41326
+ description: "Warns when catch callbacks return unknown instead of typed errors",
41327
+ defaultSeverity: "warning",
41328
+ fixable: false,
41329
+ supportedEffect: [
41330
+ "v3",
41331
+ "v4"
41332
+ ],
41333
+ preview: {
41334
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.tryPromise({\n try: async () => 1,\n catch: (error) => error\n})\n',
41335
+ diagnostics: [
41336
+ {
41337
+ start: 56,
41338
+ end: 73,
41339
+ text: "The 'catch' callback in Effect.tryPromise returns 'unknown'. The catch callback should be used to provide typed errors.\nConsider wrapping unknown errors into Effect's Data.TaggedError for example, or narrow down the type to the specific error raised. effect(unknownInEffectCatch)"
41340
+ }
41341
+ ]
41342
+ }
41343
+ },
41344
+ {
41345
+ name: "extendsNativeError",
41346
+ group: "effectNative",
41347
+ description: "Warns when a class directly extends the native Error class",
41348
+ defaultSeverity: "off",
41349
+ fixable: false,
41350
+ supportedEffect: [
41351
+ "v3",
41352
+ "v4"
41353
+ ],
41354
+ preview: {
41355
+ sourceText: "\nexport class PreviewError extends Error {}\n",
41356
+ diagnostics: [
41357
+ {
41358
+ start: 14,
41359
+ end: 26,
41360
+ text: "Avoid extending the native 'Error' class directly. Consider using a tagged error (e.g. Data.TaggedError) to maintain type safety in the Effect failure channel. effect(extendsNativeError)"
41361
+ }
41362
+ ]
41363
+ }
41364
+ },
41365
+ {
41366
+ name: "globalFetch",
41367
+ group: "effectNative",
41368
+ description: "Warns when using the global fetch function instead of the Effect HTTP client",
41369
+ defaultSeverity: "off",
41370
+ fixable: false,
41371
+ supportedEffect: [
41372
+ "v3",
41373
+ "v4"
41374
+ ],
41375
+ preview: {
41376
+ sourceText: '\nexport const preview = fetch("https://example.com")\n',
41377
+ diagnostics: [
41378
+ {
41379
+ start: 24,
41380
+ end: 29,
41381
+ text: "Prefer using HttpClient from @effect/platform instead of the global 'fetch' function. effect(globalFetch)"
41382
+ }
41383
+ ]
41384
+ }
41385
+ },
41386
+ {
41387
+ name: "instanceOfSchema",
41388
+ group: "effectNative",
41389
+ description: "Suggests using Schema.is instead of instanceof for Effect Schema types",
41390
+ defaultSeverity: "off",
41391
+ fixable: true,
41392
+ supportedEffect: [
41393
+ "v3",
41394
+ "v4"
41395
+ ],
41396
+ preview: {
41397
+ sourceText: 'import { Schema } from "effect"\n\nclass User extends Schema.Class<User>("User")({ name: Schema.String }) {}\ndeclare const value: unknown\nexport const preview = value instanceof User\n',
41398
+ diagnostics: [
41399
+ {
41400
+ start: 159,
41401
+ end: 180,
41402
+ text: "Consider using Schema.is instead of instanceof for Effect Schema types. effect(instanceOfSchema)"
41403
+ }
41404
+ ]
41405
+ }
41406
+ },
41407
+ {
41408
+ name: "nodeBuiltinImport",
41409
+ group: "effectNative",
41410
+ description: "Warns when importing Node.js built-in modules that have Effect-native counterparts",
41411
+ defaultSeverity: "off",
41412
+ fixable: false,
41413
+ supportedEffect: [
41414
+ "v3",
41415
+ "v4"
41416
+ ],
41417
+ preview: {
41418
+ sourceText: 'import fs from "node:fs"\n\nexport const preview = fs.readFileSync\n',
41419
+ diagnostics: [
41420
+ {
41421
+ start: 15,
41422
+ end: 24,
41423
+ text: "Prefer using FileSystem from @effect/platform instead of the Node.js 'fs' module. effect(nodeBuiltinImport)"
41424
+ }
41425
+ ]
41426
+ }
41427
+ },
41428
+ {
41429
+ name: "preferSchemaOverJson",
41430
+ group: "effectNative",
41431
+ description: "Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify which may throw",
41432
+ defaultSeverity: "suggestion",
41433
+ fixable: false,
41434
+ supportedEffect: [
41435
+ "v3",
41436
+ "v4"
41437
+ ],
41438
+ preview: {
41439
+ sourceText: `import { Effect } from "effect"
41440
+
41441
+ export const preview = Effect.gen(function*() {
41442
+ const text = yield* Effect.succeed('{"ok":true}')
41443
+ return JSON.parse(text)
41444
+ })
41445
+ `,
41446
+ diagnostics: [
41447
+ {
41448
+ start: 142,
41449
+ end: 158,
41450
+ text: "Consider using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify effect(preferSchemaOverJson)"
41451
+ }
41452
+ ]
41453
+ }
41454
+ },
41455
+ {
41456
+ name: "catchAllToMapError",
41457
+ group: "style",
41458
+ description: "Suggests using Effect.mapError instead of Effect.catchAll when the callback only wraps the error with Effect.fail",
41459
+ defaultSeverity: "suggestion",
41460
+ fixable: true,
41461
+ supportedEffect: [
41462
+ "v3",
41463
+ "v4"
41464
+ ],
41465
+ preview: {
41466
+ sourceText: 'import { Effect } from "effect"\n\nclass WrappedError {\n constructor(readonly cause: unknown) {}\n}\n\nexport const preview = Effect.fail("boom").pipe(\n Effect.catch((cause) => Effect.fail(new WrappedError(cause)))\n)\n',
41467
+ diagnostics: [
41468
+ {
41469
+ start: 150,
41470
+ end: 162,
41471
+ text: "You can use Effect.mapError instead of Effect.catch + Effect.fail to transform the error type. effect(catchAllToMapError)"
41472
+ }
41473
+ ]
41474
+ }
41475
+ },
41476
+ {
41477
+ name: "deterministicKeys",
41478
+ group: "style",
41479
+ description: "Enforces deterministic naming for service/tag/error identifiers based on class names",
41480
+ defaultSeverity: "off",
41481
+ fixable: true,
41482
+ supportedEffect: [
41483
+ "v3",
41484
+ "v4"
41485
+ ],
41486
+ preview: {
41487
+ sourceText: 'import { ServiceMap } from "effect"\n\nexport class RenamedService\n extends ServiceMap.Service<RenamedService, {}>()("CustomIdentifier") {}\n',
41488
+ diagnostics: [
41489
+ {
41490
+ start: 116,
41491
+ end: 134,
41492
+ text: "Key should be '@effect/harness-effect-v4/examples/diagnostics/deterministicKeys_preview/RenamedService' effect(deterministicKeys)"
41493
+ }
41494
+ ]
41495
+ }
41496
+ },
41497
+ {
41498
+ name: "effectFnOpportunity",
41499
+ group: "style",
41500
+ description: "Suggests using Effect.fn for functions that returns an Effect",
41501
+ defaultSeverity: "suggestion",
41502
+ fixable: true,
41503
+ supportedEffect: [
41504
+ "v3",
41505
+ "v4"
41506
+ ],
41507
+ preview: {
41508
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = () => Effect.gen(function*() {\n return yield* Effect.succeed(1)\n})\n',
41509
+ diagnostics: [
41510
+ {
41511
+ start: 54,
41512
+ end: 61,
41513
+ text: "Can be rewritten as a reusable function: Effect.fn(function*() { ... }) effect(effectFnOpportunity)"
41514
+ }
41515
+ ]
41516
+ }
41517
+ },
41518
+ {
41519
+ name: "effectMapVoid",
41520
+ group: "style",
41521
+ description: "Suggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {})",
41522
+ defaultSeverity: "suggestion",
41523
+ fixable: true,
41524
+ supportedEffect: [
41525
+ "v3",
41526
+ "v4"
41527
+ ],
41528
+ preview: {
41529
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.succeed(1).pipe(\n Effect.map(() => undefined)\n)\n',
41530
+ diagnostics: [
41531
+ {
41532
+ start: 82,
41533
+ end: 92,
41534
+ text: "Effect.asVoid can be used instead to discard the success value effect(effectMapVoid)"
41535
+ }
41536
+ ]
41537
+ }
41538
+ },
41539
+ {
41540
+ name: "effectSucceedWithVoid",
41541
+ group: "style",
41542
+ description: "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
41543
+ defaultSeverity: "suggestion",
41544
+ fixable: true,
41545
+ supportedEffect: [
41546
+ "v3",
41547
+ "v4"
41548
+ ],
41549
+ preview: {
41550
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.succeed(undefined)\n',
41551
+ diagnostics: [
41552
+ {
41553
+ start: 56,
41554
+ end: 81,
41555
+ text: "Effect.void can be used instead of Effect.succeed(undefined) or Effect.succeed(void 0) effect(effectSucceedWithVoid)"
41556
+ }
41557
+ ]
41558
+ }
41559
+ },
41560
+ {
41561
+ name: "importFromBarrel",
41562
+ group: "style",
41563
+ description: "Suggests importing from specific module paths instead of barrel exports",
41564
+ defaultSeverity: "off",
41565
+ fixable: true,
41566
+ supportedEffect: [
41567
+ "v3",
41568
+ "v4"
41569
+ ],
41570
+ preview: {
41571
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.void\n',
41572
+ diagnostics: [
41573
+ {
41574
+ start: 9,
41575
+ end: 15,
41576
+ text: "Importing from barrel module effect is not allowed. effect(importFromBarrel)"
41577
+ }
41578
+ ]
41579
+ }
41580
+ },
41581
+ {
41582
+ name: "missedPipeableOpportunity",
41583
+ group: "style",
41584
+ description: "Enforces the use of pipeable style for nested function calls",
41585
+ defaultSeverity: "off",
41586
+ fixable: true,
41587
+ supportedEffect: [
41588
+ "v3",
41589
+ "v4"
41590
+ ],
41591
+ preview: {
41592
+ sourceText: 'import { identity, Schema } from "effect"\n\nconst User = Schema.Struct({ id: Schema.Number })\nexport const preview = identity(identity(Schema.decodeEffect(User)({ id: 1 })))\n',
41593
+ diagnostics: [
41594
+ {
41595
+ start: 116,
41596
+ end: 172,
41597
+ text: "Nested function calls can be converted to pipeable style for better readability; consider using Schema.decodeEffect(User)({ id: 1 }).pipe(...) instead. effect(missedPipeableOpportunity)"
41598
+ }
41599
+ ]
41600
+ }
41601
+ },
41602
+ {
41603
+ name: "missingEffectServiceDependency",
41604
+ group: "style",
41605
+ description: "Checks that Effect.Service dependencies satisfy all required layer inputs",
41606
+ defaultSeverity: "off",
41607
+ fixable: false,
41608
+ supportedEffect: [
41609
+ "v3"
41610
+ ],
41611
+ preview: {
41612
+ sourceText: 'import * as Effect from "effect/Effect"\n\nclass Db extends Effect.Service<Db>()("Db", { succeed: { ok: true } }) {}\nexport class Repo extends Effect.Service<Repo>()("Repo", {\n effect: Effect.gen(function*() {\n yield* Db\n return { all: Effect.succeed([] as Array<number>) }\n })\n}) {}\n',
41613
+ diagnostics: [
41614
+ {
41615
+ start: 128,
41616
+ end: 132,
41617
+ text: "Service 'Db' is required but not provided by dependencies effect(missingEffectServiceDependency)"
41618
+ }
41619
+ ]
41620
+ }
41621
+ },
41622
+ {
41623
+ name: "redundantSchemaTagIdentifier",
41624
+ group: "style",
41625
+ description: "Suggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest",
41626
+ defaultSeverity: "suggestion",
41627
+ fixable: true,
41628
+ supportedEffect: [
41629
+ "v3",
41630
+ "v4"
41631
+ ],
41632
+ preview: {
41633
+ sourceText: 'import * as Schema from "effect/Schema"\n\nexport class Preview\n extends Schema.TaggedClass<Preview>("Preview")("Preview", {\n value: Schema.String\n }) {}\n',
41634
+ diagnostics: [
41635
+ {
41636
+ start: 100,
41637
+ end: 109,
41638
+ text: "Identifier 'Preview' is redundant since it equals the _tag value effect(redundantSchemaTagIdentifier)"
41639
+ }
41640
+ ]
41641
+ }
41642
+ },
41643
+ {
41644
+ name: "schemaStructWithTag",
41645
+ group: "style",
41646
+ description: "Suggests using Schema.TaggedStruct instead of Schema.Struct with _tag field",
41647
+ defaultSeverity: "suggestion",
41648
+ fixable: true,
41649
+ supportedEffect: [
41650
+ "v3",
41651
+ "v4"
41652
+ ],
41653
+ preview: {
41654
+ sourceText: 'import * as Schema from "effect/Schema"\n\nexport const preview = Schema.Struct({\n _tag: Schema.Literal("User"),\n name: Schema.String\n})\n',
41655
+ diagnostics: [
41656
+ {
41657
+ start: 64,
41658
+ end: 136,
41659
+ text: "Schema.Struct with a _tag field can be simplified to Schema.TaggedStruct to make the tag optional in the constructor. effect(schemaStructWithTag)"
41660
+ }
41661
+ ]
41662
+ }
41663
+ },
41664
+ {
41665
+ name: "schemaUnionOfLiterals",
41666
+ group: "style",
41667
+ description: "Simplifies Schema.Union of multiple Schema.Literal calls into single Schema.Literal",
41668
+ defaultSeverity: "off",
41669
+ fixable: true,
41670
+ supportedEffect: [
41671
+ "v3"
41672
+ ],
41673
+ preview: {
41674
+ sourceText: 'import * as Schema from "effect/Schema"\n\nexport const preview = Schema.Union(Schema.Literal("a"), Schema.Literal("b"))\n',
41675
+ diagnostics: [
41676
+ {
41677
+ start: 64,
41678
+ end: 118,
41679
+ text: "A Schema.Union of multiple Schema.Literal calls can be simplified to a single Schema.Literal call. effect(schemaUnionOfLiterals)"
41680
+ }
41681
+ ]
41682
+ }
41683
+ },
41684
+ {
41685
+ name: "serviceNotAsClass",
41686
+ group: "style",
41687
+ description: "Warns when ServiceMap.Service is used as a variable instead of a class declaration",
41688
+ defaultSeverity: "off",
41689
+ fixable: true,
41690
+ supportedEffect: [
41691
+ "v4"
41692
+ ],
41693
+ preview: {
41694
+ sourceText: 'import { ServiceMap } from "effect"\n\nexport const Preview = ServiceMap.Service<{ port: number }>("Preview")\n',
41695
+ diagnostics: [
41696
+ {
41697
+ start: 60,
41698
+ end: 107,
41699
+ text: 'ServiceMap.Service should be used in a class declaration instead of as a variable. Use: class Preview extends ServiceMap.Service<Preview, { port: number }>()("Preview") {} effect(serviceNotAsClass)'
41700
+ }
41701
+ ]
41702
+ }
41703
+ },
41704
+ {
41705
+ name: "strictBooleanExpressions",
41706
+ group: "style",
41707
+ description: "Enforces boolean types in conditional expressions for type safety",
41708
+ defaultSeverity: "off",
41709
+ fixable: false,
41710
+ supportedEffect: [
41711
+ "v3",
41712
+ "v4"
41713
+ ],
41714
+ preview: {
41715
+ sourceText: "\ndeclare const value: string | undefined\nexport const preview = value ? 1 : 0\n",
41716
+ diagnostics: [
41717
+ {
41718
+ start: 64,
41719
+ end: 69,
41720
+ text: "Unexpected `string` type in condition, expected strictly a boolean instead. effect(strictBooleanExpressions)"
41721
+ },
41722
+ {
41723
+ start: 64,
41724
+ end: 69,
41725
+ text: "Unexpected `undefined` type in condition, expected strictly a boolean instead. effect(strictBooleanExpressions)"
41726
+ }
41727
+ ]
41728
+ }
41729
+ },
41730
+ {
41731
+ name: "unnecessaryEffectGen",
41732
+ group: "style",
41733
+ description: "Suggests removing Effect.gen when it contains only a single return statement",
41734
+ defaultSeverity: "suggestion",
41735
+ fixable: true,
41736
+ supportedEffect: [
41737
+ "v3",
41738
+ "v4"
41739
+ ],
41740
+ preview: {
41741
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n return yield* Effect.succeed(1)\n})\n',
41742
+ diagnostics: [
41743
+ {
41744
+ start: 64,
41745
+ end: 125,
41746
+ text: "This Effect.gen contains a single return statement. effect(unnecessaryEffectGen)"
41747
+ }
41748
+ ]
41749
+ }
41750
+ },
41751
+ {
41752
+ name: "unnecessaryFailYieldableError",
41753
+ group: "style",
41754
+ description: "Suggests yielding yieldable errors directly instead of wrapping with Effect.fail",
41755
+ defaultSeverity: "suggestion",
41756
+ fixable: true,
41757
+ supportedEffect: [
41758
+ "v3",
41759
+ "v4"
41760
+ ],
41761
+ preview: {
41762
+ sourceText: 'import * as Data from "effect/Data"\nimport * as Effect from "effect/Effect"\n\nclass Boom extends Data.TaggedError("Boom")<{}> {}\nexport const preview = Effect.gen(function*() {\n yield* Effect.fail(new Boom())\n})\n',
41763
+ diagnostics: [
41764
+ {
41765
+ start: 178,
41766
+ end: 208,
41767
+ text: "This Effect.fail call uses a yieldable error type as argument. You can yield* the error directly instead. effect(unnecessaryFailYieldableError)"
41768
+ }
41769
+ ]
41770
+ }
41771
+ },
41772
+ {
41773
+ name: "unnecessaryPipe",
41774
+ group: "style",
41775
+ description: "Removes pipe calls with no arguments",
41776
+ defaultSeverity: "suggestion",
41777
+ fixable: true,
41778
+ supportedEffect: [
41779
+ "v3",
41780
+ "v4"
41781
+ ],
41782
+ preview: {
41783
+ sourceText: 'import { pipe } from "effect/Function"\n\nexport const preview = pipe(1)\n',
41784
+ diagnostics: [
41785
+ {
41786
+ start: 63,
41787
+ end: 70,
41788
+ text: "This pipe call contains no arguments. effect(unnecessaryPipe)"
41789
+ }
41790
+ ]
41791
+ }
41792
+ },
41793
+ {
41794
+ name: "unnecessaryPipeChain",
41795
+ group: "style",
41796
+ description: "Simplifies chained pipe calls into a single pipe call",
41797
+ defaultSeverity: "suggestion",
41798
+ fixable: true,
41799
+ supportedEffect: [
41800
+ "v3",
41801
+ "v4"
41802
+ ],
41803
+ preview: {
41804
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.succeed(1).pipe(Effect.asVoid).pipe(Effect.as("done"))\n',
41805
+ diagnostics: [
41806
+ {
41807
+ start: 64,
41808
+ end: 125,
41809
+ text: "Chained pipe calls can be simplified to a single pipe call effect(unnecessaryPipeChain)"
41810
+ }
41811
+ ]
41812
+ }
41813
+ }
41814
+ ]
41815
+ };
41816
+
41817
+ // src/cli/setup/diagnostic-info.ts
41818
+ var diagnosticMetadata = metadata_default;
41819
+ function getDiagnosticGroups() {
41820
+ return diagnosticMetadata.groups;
41821
+ }
41822
+ function getDiagnosticMetadataRules() {
41823
+ return diagnosticMetadata.rules;
41824
+ }
41825
+ function getAllDiagnostics() {
41826
+ const diagnosticsByName = new Map(diagnostics.map((diagnostic) => [diagnostic.name, diagnostic]));
41827
+ return getDiagnosticMetadataRules().flatMap((metadataRule) => {
41828
+ const diagnostic = diagnosticsByName.get(metadataRule.name);
41829
+ if (!diagnostic) {
41830
+ return [];
41831
+ }
41832
+ return [{
41833
+ name: diagnostic.name,
41834
+ code: diagnostic.code,
41835
+ group: metadataRule.group,
41836
+ defaultSeverity: diagnostic.severity,
41837
+ description: metadataRule.description,
41838
+ preview: metadataRule.preview
41839
+ }];
41840
+ });
41841
+ }
41842
+ function cycleSeverity(current, direction) {
41843
+ const order = ["off", "suggestion", "message", "warning", "error"];
41844
+ const currentIndex = order.indexOf(current);
41845
+ if (direction === "right") {
41846
+ return order[(currentIndex + 1) % order.length];
41847
+ } else {
41848
+ return order[(currentIndex - 1 + order.length) % order.length];
41849
+ }
41850
+ }
41851
+ var shortNames = {
41852
+ off: "off",
41853
+ suggestion: "sugg",
41854
+ message: "info",
41855
+ warning: "warn",
41856
+ error: "err"
41857
+ };
41858
+ var MAX_SEVERITY_LENGTH = Object.values(shortNames).reduce((max2, name) => Math.max(max2, name.length), 0);
41859
+ function getSeverityShortName(severity) {
41860
+ return shortNames[severity] ?? "???";
41861
+ }
41862
+
41863
+ // src/cli/setup/diagnostic-prompt.ts
41864
+ var diagnosticGroups = getDiagnosticGroups();
41865
+ var Action2 = taggedEnum();
41866
+ var SEARCH_ICON = "/";
41867
+ var MIN_PREVIEW_AND_MESSAGES_LINES = 18;
41868
+ function getControlsLegend(searchText) {
41869
+ const searchLegend = searchText.length === 0 ? `${SEARCH_ICON} type to search` : `${SEARCH_ICON} searching: ${searchText}`;
41870
+ return `\u2190/\u2192 change rule \u2191/\u2193 change severity ${searchLegend}`;
41871
+ }
41872
+ function getSeveritySymbol(severity) {
41873
+ const symbols = {
41874
+ off: ".",
41875
+ suggestion: "?",
41876
+ message: "i",
41877
+ warning: "!",
41878
+ error: "x"
41879
+ };
41880
+ return symbols[severity];
41881
+ }
41882
+ function getSeverityStyle(severity) {
41883
+ const styles = {
41884
+ off: DIM,
41885
+ suggestion: DIM,
41886
+ message: BLUE,
41887
+ warning: YELLOW,
41888
+ error: RED
41889
+ };
41890
+ return styles[severity];
41891
+ }
41892
+ function renderEntry(entry, severity, isSelected) {
41893
+ const symbol4 = ansi(getSeveritySymbol(severity), getSeverityStyle(severity));
41894
+ const name = isSelected ? ansi(entry.name, UNDERLINE) : entry.name;
41895
+ return `${symbol4} ${name}`;
41896
+ }
41897
+ function rowsForLength(length, columns) {
41898
+ if (columns <= 0) {
41899
+ return 1;
41900
+ }
41901
+ return Math.max(1, 1 + Math.floor(Math.max(length - 1, 0) / columns));
41902
+ }
41903
+ function eraseRenderedLines(lines2, columns) {
41904
+ let result3 = "";
41905
+ for (let lineIndex = lines2.length - 1; lineIndex >= 0; lineIndex--) {
41906
+ const rows = rowsForLength(visibleLength(lines2[lineIndex] ?? ""), columns);
41907
+ for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
41908
+ result3 += ERASE_LINE;
41909
+ if (!(lineIndex === 0 && rowIndex === rows - 1)) {
41910
+ result3 += "\x1B[1A";
41911
+ }
41912
+ }
41913
+ }
41914
+ if (lines2.length > 0) {
41915
+ result3 += CURSOR_LEFT;
41916
+ }
41917
+ return result3;
41918
+ }
41919
+ function wrapPaddedText(text2, initialPadding, endPadding, columns) {
41920
+ if (text2.length === 0) {
41921
+ return [initialPadding + endPadding];
41922
+ }
41923
+ const available = Math.max(columns - visibleLength(initialPadding) - visibleLength(endPadding), 1);
41924
+ const lines2 = [];
41925
+ let remaining = text2;
41926
+ while (remaining.length > 0) {
41927
+ const chunk = remaining.slice(0, available);
41928
+ lines2.push(initialPadding + chunk + endPadding);
41929
+ remaining = remaining.slice(chunk.length);
41930
+ }
41931
+ return lines2;
41932
+ }
41933
+ function wrapListItemText(text2, firstLinePadding, continuationPadding, endPadding, columns) {
41934
+ if (text2.length === 0) {
41935
+ return [firstLinePadding + endPadding];
41936
+ }
41937
+ const firstAvailable = Math.max(columns - visibleLength(firstLinePadding) - visibleLength(endPadding), 1);
41938
+ const continuationAvailable = Math.max(
41939
+ columns - visibleLength(continuationPadding) - visibleLength(endPadding),
41940
+ 1
41941
+ );
41942
+ const lines2 = [];
41943
+ let remaining = text2;
41944
+ let isFirstLine = true;
41945
+ while (remaining.length > 0) {
41946
+ const padding = isFirstLine ? firstLinePadding : continuationPadding;
41947
+ const available = isFirstLine ? firstAvailable : continuationAvailable;
41948
+ const chunk = remaining.slice(0, available);
41949
+ lines2.push(padding + chunk + endPadding);
41950
+ remaining = remaining.slice(chunk.length);
41951
+ isFirstLine = false;
41952
+ }
41953
+ return lines2;
41954
+ }
41955
+ function renderPaddedLine(text2, initialPadding, endPadding, columns) {
41956
+ const available = Math.max(columns - visibleLength(initialPadding) - visibleLength(endPadding), 0);
41957
+ const truncated = visibleLength(text2) <= available ? text2 : text2.slice(0, available);
41958
+ const padding = Math.max(available - visibleLength(truncated), 0);
41959
+ return initialPadding + truncated + " ".repeat(padding) + endPadding;
41960
+ }
41961
+ function mergeHighlightRanges(ranges) {
41962
+ const sorted = ranges.filter((range2) => range2.end > range2.start).slice().sort((a, b) => a.start - b.start);
41963
+ const merged = [];
41964
+ for (const range2 of sorted) {
41965
+ const previous = merged[merged.length - 1];
41966
+ if (!previous || range2.start > previous.end) {
41967
+ merged.push(range2);
41968
+ continue;
41969
+ }
41970
+ merged[merged.length - 1] = {
41971
+ start: previous.start,
41972
+ end: Math.max(previous.end, range2.end)
41973
+ };
41974
+ }
41975
+ return merged;
41976
+ }
41977
+ function stylePreviewLine(line, lineStart, ranges) {
41978
+ if (line.length === 0) {
41979
+ return "";
41980
+ }
41981
+ const lineEnd = lineStart + line.length;
41982
+ let cursor = 0;
41983
+ let rendered = "";
41984
+ for (const range2 of ranges) {
41985
+ const start = Math.max(range2.start, lineStart);
41986
+ const end = Math.min(range2.end, lineEnd);
41987
+ if (end <= start) {
41988
+ continue;
41989
+ }
41990
+ const startIndex = start - lineStart;
41991
+ const endIndex = end - lineStart;
41992
+ if (cursor < startIndex) {
41993
+ rendered += ansi(line.slice(cursor, startIndex), DIM);
41994
+ }
41995
+ rendered += ansi(line.slice(startIndex, endIndex), UNDERLINE);
41996
+ cursor = endIndex;
41997
+ }
41998
+ if (cursor < line.length) {
41999
+ rendered += ansi(line.slice(cursor), DIM);
42000
+ }
42001
+ return rendered;
42002
+ }
42003
+ function wrapSourceLine(line, lineStart, availableWidth) {
42004
+ if (line.length === 0) {
42005
+ return [{ text: "", start: lineStart, end: lineStart }];
42006
+ }
42007
+ const width = Math.max(availableWidth, 1);
42008
+ const wrapped = [];
42009
+ let offset = 0;
42010
+ while (offset < line.length) {
42011
+ const text2 = line.slice(offset, offset + width);
42012
+ wrapped.push({
42013
+ text: text2,
42014
+ start: lineStart + offset,
42015
+ end: lineStart + offset + text2.length
42016
+ });
42017
+ offset += text2.length;
42018
+ }
42019
+ return wrapped;
42020
+ }
42021
+ function wrapPreviewSourceText(sourceText, columns) {
42022
+ const availableWidth = Math.max(columns - 2, 1);
42023
+ const logicalLines = sourceText.split("\n");
42024
+ const wrapped = [];
42025
+ let offset = 0;
42026
+ for (const line of logicalLines) {
42027
+ for (const wrappedLine of wrapSourceLine(line, offset, availableWidth)) {
42028
+ wrapped.push(wrappedLine);
42029
+ }
42030
+ offset += line.length + 1;
42031
+ }
42032
+ return wrapped;
42033
+ }
42034
+ function renderPreviewSourceText(sourceText, diagnostics3, columns) {
42035
+ const ranges = mergeHighlightRanges(diagnostics3.map((diagnostic) => ({
42036
+ start: diagnostic.start,
42037
+ end: diagnostic.end
42038
+ })));
42039
+ return wrapPreviewSourceText(sourceText, columns).map((line) => {
42040
+ const rendered = stylePreviewLine(line.text, line.start, ranges);
42041
+ return CURSOR_TO_0 + renderPaddedLine(rendered, " ", "", columns);
42042
+ });
42043
+ }
42044
+ function renderPreviewMessages(diagnostics3, columns) {
42045
+ const messages = Array.from(new Set(diagnostics3.map((diagnostic) => diagnostic.text)));
42046
+ return messages.flatMap((message) => {
42047
+ let isFirstBlock = true;
42048
+ return message.split("\n").flatMap((line) => {
42049
+ const wrappedLines = wrapListItemText(line, isFirstBlock ? "- " : " ", " ", "", columns);
42050
+ isFirstBlock = false;
42051
+ return wrappedLines.map((wrappedLine) => CURSOR_TO_0 + ansi(wrappedLine, DIM + ITALIC));
42052
+ });
42053
+ });
42054
+ }
42055
+ function renderDivider(columns, legendText) {
42056
+ if (legendText === void 0) {
42057
+ return ansi("\u2500".repeat(Math.max(columns, 0)), DIM);
42058
+ }
42059
+ const legend = ` ${legendText} `;
42060
+ const legendLength = visibleLength(legend);
42061
+ if (columns <= legendLength) {
42062
+ return ansi(legend.slice(0, Math.max(columns, 0)), DIM);
42063
+ }
42064
+ const remaining = columns - legendLength;
42065
+ const left = "\u2500".repeat(Math.floor(remaining / 2));
42066
+ const right = "\u2500".repeat(remaining - left.length);
42067
+ return ansi(left + legend + right, DIM);
42068
+ }
42069
+ function renderSelectedRuleDivider(selected, severities, columns) {
42070
+ if (!selected) {
42071
+ return renderDivider(columns);
42072
+ }
42073
+ const currentSeverity = severities[selected.name] ?? selected.defaultSeverity;
42074
+ const text2 = `${selected.name} (currently set as ${getSeverityShortName(currentSeverity)})`;
42075
+ return renderDivider(columns, text2);
42076
+ }
42077
+ function matchesSearch(entry, searchText) {
42078
+ if (searchText.length === 0) {
42079
+ return true;
42080
+ }
42081
+ const normalized = searchText.toLowerCase();
42082
+ return entry.name.toLowerCase().includes(normalized) || entry.description.toLowerCase().includes(normalized) || entry.previewSourceText.toLowerCase().includes(normalized);
42083
+ }
42084
+ function getFilteredEntries(entries2, searchText) {
42085
+ return entries2.filter((entry) => matchesSearch(entry, searchText));
42086
+ }
42087
+ function normalizeStartIndex(length, startIndex) {
42088
+ if (length <= 0) {
42089
+ return 0;
42090
+ }
42091
+ return (startIndex % length + length) % length;
42092
+ }
42093
+ function isPrintableInput(input) {
42094
+ const printablePattern = new RegExp(String.raw`^[^\u0000-\u001F\u007F]+$`, "u");
42095
+ return !input.key.ctrl && !input.key.meta && input.input !== void 0 && input.input.length > 0 && printablePattern.test(input.input);
42096
+ }
42097
+ function buildVisibleEntries(entries2, severities, startIndex, columns) {
42098
+ if (entries2.length === 0) {
42099
+ return {
42100
+ fullLine: renderPaddedLine(ansi("No matching rules", DIM), " ", " ", columns),
42101
+ visibleRules: []
42102
+ };
42103
+ }
42104
+ const reservedColumns = 4;
42105
+ const itemColumns = Math.max(columns - reservedColumns, 0);
42106
+ const separator = " ";
42107
+ const visibleEntries = [];
42108
+ const visibleRules = [];
42109
+ let currentLength = 0;
42110
+ let seenCount = 0;
42111
+ while (seenCount < entries2.length) {
42112
+ const index = (startIndex + seenCount) % entries2.length;
42113
+ const rule = entries2[index];
42114
+ const currentSeverity = severities[rule.name] ?? rule.defaultSeverity;
42115
+ const entry = renderEntry(rule, currentSeverity, seenCount === 0);
42116
+ const nextLength = visibleEntries.length === 0 ? visibleLength(entry) : currentLength + separator.length + visibleLength(entry);
42117
+ if (nextLength > itemColumns) {
42118
+ break;
42119
+ }
42120
+ visibleEntries.push(entry);
42121
+ visibleRules.push(rule);
42122
+ currentLength = nextLength;
42123
+ seenCount++;
42124
+ }
42125
+ const leftMarker = entries2.length > 1 ? ansi("\u2190", DIM) : " ";
42126
+ const rightMarker = entries2.length > 1 ? ansi("\u2192", DIM) : " ";
42127
+ return {
42128
+ fullLine: renderPaddedLine(visibleEntries.join(separator), `${leftMarker} `, ` ${rightMarker}`, columns),
42129
+ visibleRules
42130
+ };
42131
+ }
42132
+ function buildGroupLine(selected, columns) {
42133
+ if (!selected) {
42134
+ return renderPaddedLine("", " ", " ", columns);
42135
+ }
42136
+ const selectedIndex = diagnosticGroups.findIndex((group) => group.id === selected.group);
42137
+ const rotatedGroups = diagnosticGroups.map(
42138
+ (_, index) => diagnosticGroups[(selectedIndex + index) % diagnosticGroups.length]
42139
+ );
42140
+ const content = rotatedGroups.map((group, index) => {
42141
+ const label = group.name.toUpperCase();
42142
+ return index === 0 ? ansi(label, BOLD) : ansi(label, DIM);
42143
+ }).join(" ");
42144
+ return renderPaddedLine(content, " ", " ", columns);
42145
+ }
42146
+ function buildFrame(entries2, searchText, severities, startIndex, columns) {
42147
+ const visible = buildVisibleEntries(entries2, severities, startIndex, columns);
42148
+ const selected = visible.visibleRules[0];
42149
+ const wrappedDescription = wrapPaddedText(selected?.description ?? "", " ", "", columns);
42150
+ const previewLines = renderPreviewSourceText(
42151
+ selected?.previewSourceText ?? "",
42152
+ selected?.previewDiagnostics ?? [],
42153
+ columns
42154
+ );
42155
+ const previewMessages = renderPreviewMessages(selected?.previewDiagnostics ?? [], columns);
42156
+ const previewSectionLines = [...previewLines, ...previewMessages];
42157
+ const paddingLines = Array.from(
42158
+ { length: Math.max(MIN_PREVIEW_AND_MESSAGES_LINES - previewSectionLines.length, 0) },
42159
+ () => ""
42160
+ );
42161
+ return [
42162
+ CURSOR_HIDE + renderSelectedRuleDivider(selected, severities, columns),
42163
+ ...previewSectionLines,
42164
+ ...paddingLines,
42165
+ CURSOR_TO_0 + renderDivider(columns),
42166
+ CURSOR_TO_0 + buildGroupLine(selected, columns),
42167
+ CURSOR_TO_0 + visible.fullLine,
42168
+ ...wrappedDescription.map((line) => CURSOR_TO_0 + ansi(line, DIM)),
42169
+ CURSOR_TO_0 + renderDivider(columns, getControlsLegend(searchText)),
42170
+ ""
42171
+ ];
42172
+ }
42173
+ function buildState(entries2, startIndex, searchText, severities) {
42174
+ return gen2(function* () {
42175
+ const terminal = yield* Terminal;
42176
+ const columns = Math.max(yield* terminal.columns, 1);
42177
+ const filteredEntries = getFilteredEntries(entries2, searchText);
42178
+ const normalizedStartIndex = normalizeStartIndex(filteredEntries.length, startIndex);
42179
+ const renderedLines = buildFrame(filteredEntries, searchText, severities, normalizedStartIndex, columns);
42180
+ return {
42181
+ startIndex: normalizedStartIndex,
42182
+ searchText,
42183
+ severities,
42184
+ renderedColumns: columns,
42185
+ renderedLines
42186
+ };
42187
+ });
42188
+ }
42189
+ function renderSubmission(state, entries2) {
42190
+ return succeed6(
42191
+ CURSOR_TO_0 + JSON.stringify(
42192
+ Object.fromEntries(
42193
+ entries2.flatMap((entry) => {
42194
+ const severity = state.severities[entry.name];
42195
+ return severity !== void 0 && severity !== entry.defaultSeverity ? [[entry.name, severity]] : [];
42196
+ })
42197
+ )
42198
+ ) + "\n"
42199
+ );
42200
+ }
42201
+ function handleProcess(entries2) {
42202
+ return (input, state) => {
42203
+ const filteredEntries = getFilteredEntries(entries2, state.searchText);
42204
+ switch (input.key.name) {
42205
+ case "backspace":
42206
+ return buildState(entries2, 0, state.searchText.slice(0, -1), state.severities).pipe(
42207
+ map6((nextState) => Action2.NextFrame({ state: nextState }))
42208
+ );
42209
+ case "left":
42210
+ if (filteredEntries.length === 0) return succeed6(Action2.Beep());
42211
+ return buildState(entries2, state.startIndex - 1, state.searchText, state.severities).pipe(
42212
+ map6((nextState) => Action2.NextFrame({ state: nextState }))
42213
+ );
42214
+ case "right":
42215
+ if (filteredEntries.length === 0) return succeed6(Action2.Beep());
42216
+ return buildState(entries2, state.startIndex + 1, state.searchText, state.severities).pipe(
42217
+ map6((nextState) => Action2.NextFrame({ state: nextState }))
42218
+ );
42219
+ case "up":
42220
+ case "down":
42221
+ if (filteredEntries.length === 0) return succeed6(Action2.Beep());
42222
+ return buildState(entries2, state.startIndex, state.searchText, {
42223
+ ...state.severities,
42224
+ [filteredEntries[state.startIndex].name]: cycleSeverity(
42225
+ state.severities[filteredEntries[state.startIndex].name] ?? filteredEntries[state.startIndex].defaultSeverity,
42226
+ input.key.name === "up" ? "left" : "right"
42227
+ )
42228
+ }).pipe(map6((nextState) => Action2.NextFrame({ state: nextState })));
42229
+ case "enter":
42230
+ case "return":
42231
+ return succeed6(Action2.Submit({
42232
+ value: Object.fromEntries(
42233
+ entries2.flatMap((entry) => {
42234
+ const severity = state.severities[entry.name];
42235
+ return severity !== void 0 && severity !== entry.defaultSeverity ? [[entry.name, severity]] : [];
42236
+ })
42237
+ )
42238
+ }));
42239
+ default:
42240
+ if (!isPrintableInput(input)) return succeed6(Action2.Beep());
42241
+ return buildState(entries2, 0, state.searchText + input.input, state.severities).pipe(
42242
+ map6((nextState) => Action2.NextFrame({ state: nextState }))
42243
+ );
42244
+ }
42245
+ };
42246
+ }
42247
+ function getPromptEntries(diagnostics3) {
42248
+ const diagnosticsByName = new Map(diagnostics3.map((diagnostic) => [diagnostic.name, diagnostic]));
42249
+ return diagnostics3.flatMap((rule) => {
42250
+ const diagnostic = diagnosticsByName.get(rule.name);
42251
+ if (!diagnostic) {
42252
+ return [];
42253
+ }
42254
+ return [{
42255
+ name: rule.name,
42256
+ group: diagnostic.group,
42257
+ description: diagnostic.description,
42258
+ previewSourceText: diagnostic.preview.sourceText,
42259
+ previewDiagnostics: diagnostic.preview.diagnostics,
42260
+ defaultSeverity: diagnostic.defaultSeverity
42261
+ }];
42262
+ });
42263
+ }
42264
+ function createDiagnosticPrompt(diagnostics3, initialSeverities) {
42265
+ const entries2 = getPromptEntries(diagnostics3);
42266
+ return custom(buildState(entries2, 0, "", initialSeverities), {
42267
+ render: (state, action2) => {
42268
+ switch (action2._tag) {
42269
+ case "Beep":
42270
+ return succeed6(BEEP);
42271
+ case "NextFrame":
42272
+ return succeed6(action2.state.renderedLines.join("\n"));
42273
+ case "Submit":
42274
+ return renderSubmission(state, entries2);
42275
+ }
42276
+ },
42277
+ process: handleProcess(entries2),
42278
+ clear: (state) => gen2(function* () {
42279
+ const terminal = yield* Terminal;
42280
+ const columns = yield* terminal.columns;
42281
+ return eraseRenderedLines(state.renderedLines, columns);
42282
+ })
40709
42283
  });
40710
42284
  }
40711
42285