@effect/language-service 0.80.0 → 0.82.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.82.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"],
@@ -32072,6 +32076,7 @@ var classSelfMismatch = createDiagnostic({
32072
32076
  if (ts.isClassDeclaration(node) && node.name && node.heritageClauses) {
32073
32077
  const result3 = yield* pipe(
32074
32078
  typeParser.extendsEffectService(node),
32079
+ orElse5(() => typeParser.extendsServiceMapService(node)),
32075
32080
  orElse5(() => typeParser.extendsContextTag(node)),
32076
32081
  orElse5(() => typeParser.extendsEffectTag(node)),
32077
32082
  orElse5(() => typeParser.extendsSchemaClass(node)),
@@ -32195,6 +32200,7 @@ var deterministicKeys = createDiagnostic({
32195
32200
  name: "deterministicKeys",
32196
32201
  code: 25,
32197
32202
  description: "Enforces deterministic naming for service/tag/error identifiers based on class names",
32203
+ group: "style",
32198
32204
  severity: "off",
32199
32205
  fixable: true,
32200
32206
  supportedEffect: ["v3", "v4"],
@@ -32314,6 +32320,7 @@ var duplicatePackage = createDiagnostic({
32314
32320
  name: "duplicatePackage",
32315
32321
  code: 6,
32316
32322
  description: "Detects when multiple versions of the same Effect package are loaded",
32323
+ group: "correctness",
32317
32324
  severity: "warning",
32318
32325
  fixable: false,
32319
32326
  supportedEffect: ["v3", "v4"],
@@ -32345,6 +32352,7 @@ var effectFnIife = createDiagnostic({
32345
32352
  name: "effectFnIife",
32346
32353
  code: 46,
32347
32354
  description: "Effect.fn or Effect.fnUntraced is called as an IIFE (Immediately Invoked Function Expression). Use Effect.gen instead.",
32355
+ group: "antipattern",
32348
32356
  severity: "warning",
32349
32357
  fixable: true,
32350
32358
  supportedEffect: ["v3", "v4"],
@@ -32444,11 +32452,95 @@ var effectFnIife = createDiagnostic({
32444
32452
  })
32445
32453
  });
32446
32454
 
32455
+ // src/diagnostics/effectFnImplicitAny.ts
32456
+ var getParameterName = (typescript, name) => {
32457
+ if (typescript.isIdentifier(name)) {
32458
+ return typescript.idText(name);
32459
+ }
32460
+ return "parameter";
32461
+ };
32462
+ var hasOuterContextualFunctionType = (typescript, typeChecker, node) => {
32463
+ const contextualType = typeChecker.getContextualType(node);
32464
+ if (!contextualType) {
32465
+ return false;
32466
+ }
32467
+ return typeChecker.getSignaturesOfType(contextualType, typescript.SignatureKind.Call).length > 0;
32468
+ };
32469
+ var effectFnImplicitAny = createDiagnostic({
32470
+ name: "effectFnImplicitAny",
32471
+ code: 54,
32472
+ description: "Mirrors noImplicitAny for unannotated Effect.fn and Effect.fnUntraced callback parameters when no outer contextual function type exists",
32473
+ group: "correctness",
32474
+ severity: "error",
32475
+ fixable: false,
32476
+ supportedEffect: ["v3", "v4"],
32477
+ apply: fn3("effectFnImplicitAny.apply")(function* (sourceFile, report) {
32478
+ const ts = yield* service2(TypeScriptApi);
32479
+ const program = yield* service2(TypeScriptProgram);
32480
+ const typeChecker = yield* service2(TypeCheckerApi);
32481
+ const typeParser = yield* service2(TypeParser);
32482
+ const noImplicitAny = program.getCompilerOptions().noImplicitAny ?? program.getCompilerOptions().strict ?? false;
32483
+ if (!noImplicitAny) {
32484
+ return;
32485
+ }
32486
+ const nodeToVisit = [sourceFile];
32487
+ const appendNodeToVisit = (node) => {
32488
+ nodeToVisit.push(node);
32489
+ return void 0;
32490
+ };
32491
+ while (nodeToVisit.length > 0) {
32492
+ const node = nodeToVisit.pop();
32493
+ ts.forEachChild(node, appendNodeToVisit);
32494
+ const parsed = yield* pipe(
32495
+ typeParser.effectFn(node),
32496
+ map12((result3) => ({
32497
+ call: result3.node,
32498
+ fn: result3.regularFunction
32499
+ })),
32500
+ orElse5(
32501
+ () => pipe(
32502
+ typeParser.effectFnGen(node),
32503
+ map12((result3) => ({
32504
+ call: result3.node,
32505
+ fn: result3.generatorFunction
32506
+ }))
32507
+ )
32508
+ ),
32509
+ orElse5(
32510
+ () => pipe(
32511
+ typeParser.effectFnUntracedGen(node),
32512
+ map12((result3) => ({
32513
+ call: result3.node,
32514
+ fn: result3.generatorFunction
32515
+ }))
32516
+ )
32517
+ ),
32518
+ orUndefined
32519
+ );
32520
+ if (!parsed || hasOuterContextualFunctionType(ts, typeChecker, parsed.call)) {
32521
+ continue;
32522
+ }
32523
+ for (const parameter of parsed.fn.parameters) {
32524
+ if (parameter.type || parameter.initializer) {
32525
+ continue;
32526
+ }
32527
+ const parameterName = getParameterName(ts, parameter.name);
32528
+ report({
32529
+ location: parameter.name,
32530
+ messageText: `Parameter '${parameterName}' implicitly has an 'any' type in Effect.fn/Effect.fnUntraced. Add an explicit type annotation or provide a contextual function type.`,
32531
+ fixes: []
32532
+ });
32533
+ }
32534
+ }
32535
+ })
32536
+ });
32537
+
32447
32538
  // src/diagnostics/effectFnOpportunity.ts
32448
32539
  var effectFnOpportunity = createDiagnostic({
32449
32540
  name: "effectFnOpportunity",
32450
32541
  code: 41,
32451
32542
  description: "Suggests using Effect.fn for functions that returns an Effect",
32543
+ group: "style",
32452
32544
  severity: "suggestion",
32453
32545
  fixable: true,
32454
32546
  supportedEffect: ["v3", "v4"],
@@ -33043,6 +33135,7 @@ var effectGenUsesAdapter = createDiagnostic({
33043
33135
  name: "effectGenUsesAdapter",
33044
33136
  code: 23,
33045
33137
  description: "Warns when using the deprecated adapter parameter in Effect.gen",
33138
+ group: "antipattern",
33046
33139
  severity: "warning",
33047
33140
  fixable: false,
33048
33141
  supportedEffect: ["v3", "v4"],
@@ -33083,6 +33176,7 @@ var effectInFailure = createDiagnostic({
33083
33176
  name: "effectInFailure",
33084
33177
  code: 49,
33085
33178
  description: "Warns when an Effect is used inside an Effect failure channel",
33179
+ group: "antipattern",
33086
33180
  severity: "warning",
33087
33181
  fixable: false,
33088
33182
  supportedEffect: ["v3", "v4"],
@@ -33149,6 +33243,7 @@ var effectInVoidSuccess = createDiagnostic({
33149
33243
  name: "effectInVoidSuccess",
33150
33244
  code: 14,
33151
33245
  description: "Detects nested Effects in void success channels that may cause unexecuted effects",
33246
+ group: "antipattern",
33152
33247
  severity: "warning",
33153
33248
  fixable: false,
33154
33249
  supportedEffect: ["v3", "v4"],
@@ -33200,6 +33295,7 @@ var effectMapVoid = createDiagnostic({
33200
33295
  name: "effectMapVoid",
33201
33296
  code: 40,
33202
33297
  description: "Suggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {})",
33298
+ group: "style",
33203
33299
  severity: "suggestion",
33204
33300
  fixable: true,
33205
33301
  supportedEffect: ["v3", "v4"],
@@ -33266,6 +33362,7 @@ var effectSucceedWithVoid = createDiagnostic({
33266
33362
  name: "effectSucceedWithVoid",
33267
33363
  code: 47,
33268
33364
  description: "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
33365
+ group: "style",
33269
33366
  severity: "suggestion",
33270
33367
  fixable: true,
33271
33368
  supportedEffect: ["v3", "v4"],
@@ -33319,6 +33416,7 @@ var extendsNativeError = createDiagnostic({
33319
33416
  name: "extendsNativeError",
33320
33417
  code: 50,
33321
33418
  description: "Warns when a class directly extends the native Error class",
33419
+ group: "effectNative",
33322
33420
  severity: "off",
33323
33421
  fixable: false,
33324
33422
  supportedEffect: ["v3", "v4"],
@@ -33371,6 +33469,7 @@ var floatingEffect = createDiagnostic({
33371
33469
  name: "floatingEffect",
33372
33470
  code: 3,
33373
33471
  description: "Ensures Effects are yielded or assigned to variables, not left floating",
33472
+ group: "correctness",
33374
33473
  severity: "error",
33375
33474
  fixable: false,
33376
33475
  supportedEffect: ["v3", "v4"],
@@ -33424,6 +33523,7 @@ var genericEffectServices = createDiagnostic({
33424
33523
  name: "genericEffectServices",
33425
33524
  code: 10,
33426
33525
  description: "Prevents services with type parameters that cannot be discriminated at runtime",
33526
+ group: "correctness",
33427
33527
  severity: "warning",
33428
33528
  fixable: false,
33429
33529
  supportedEffect: ["v3", "v4"],
@@ -33473,6 +33573,7 @@ var globalErrorInEffectCatch = createDiagnostic({
33473
33573
  name: "globalErrorInEffectCatch",
33474
33574
  code: 36,
33475
33575
  description: "Warns when catch callbacks return global Error type instead of typed errors",
33576
+ group: "antipattern",
33476
33577
  severity: "warning",
33477
33578
  fixable: false,
33478
33579
  supportedEffect: ["v3", "v4"],
@@ -33535,6 +33636,7 @@ var globalErrorInEffectFailure = createDiagnostic({
33535
33636
  name: "globalErrorInEffectFailure",
33536
33637
  code: 35,
33537
33638
  description: "Warns when the global Error type is used in an Effect failure channel",
33639
+ group: "antipattern",
33538
33640
  severity: "warning",
33539
33641
  fixable: false,
33540
33642
  supportedEffect: ["v3", "v4"],
@@ -33585,11 +33687,54 @@ var globalErrorInEffectFailure = createDiagnostic({
33585
33687
  })
33586
33688
  });
33587
33689
 
33690
+ // src/diagnostics/globalFetch.ts
33691
+ var globalFetch = createDiagnostic({
33692
+ name: "globalFetch",
33693
+ code: 53,
33694
+ description: "Warns when using the global fetch function instead of the Effect HTTP client",
33695
+ group: "effectNative",
33696
+ severity: "off",
33697
+ fixable: false,
33698
+ supportedEffect: ["v3", "v4"],
33699
+ apply: fn3("globalFetch.apply")(function* (sourceFile, report) {
33700
+ const ts = yield* service2(TypeScriptApi);
33701
+ const typeChecker = yield* service2(TypeCheckerApi);
33702
+ const typeParser = yield* service2(TypeParser);
33703
+ const fetchSymbol = typeChecker.resolveName("fetch", void 0, ts.SymbolFlags.Value, false);
33704
+ if (!fetchSymbol) return;
33705
+ const effectVersion = typeParser.supportedEffect();
33706
+ const packageName = effectVersion === "v3" ? "@effect/platform" : "effect/unstable/http";
33707
+ const messageText = `Prefer using HttpClient from ${packageName} instead of the global 'fetch' function.`;
33708
+ const nodeToVisit = [];
33709
+ const appendNodeToVisit = (node) => {
33710
+ nodeToVisit.push(node);
33711
+ return void 0;
33712
+ };
33713
+ ts.forEachChild(sourceFile, appendNodeToVisit);
33714
+ while (nodeToVisit.length > 0) {
33715
+ const node = nodeToVisit.shift();
33716
+ if (ts.isCallExpression(node)) {
33717
+ const symbol4 = typeChecker.getSymbolAtLocation(node.expression);
33718
+ const resolvedSymbol = symbol4 && symbol4.flags & ts.SymbolFlags.Alias ? typeChecker.getAliasedSymbol(symbol4) : symbol4;
33719
+ if (resolvedSymbol === fetchSymbol) {
33720
+ report({
33721
+ location: node.expression,
33722
+ messageText,
33723
+ fixes: []
33724
+ });
33725
+ }
33726
+ }
33727
+ ts.forEachChild(node, appendNodeToVisit);
33728
+ }
33729
+ })
33730
+ });
33731
+
33588
33732
  // src/diagnostics/importFromBarrel.ts
33589
33733
  var importFromBarrel = createDiagnostic({
33590
33734
  name: "importFromBarrel",
33591
33735
  code: 12,
33592
33736
  description: "Suggests importing from specific module paths instead of barrel exports",
33737
+ group: "style",
33593
33738
  severity: "off",
33594
33739
  fixable: true,
33595
33740
  supportedEffect: ["v3", "v4"],
@@ -33732,6 +33877,7 @@ var instanceOfSchema = createDiagnostic({
33732
33877
  name: "instanceOfSchema",
33733
33878
  code: 45,
33734
33879
  description: "Suggests using Schema.is instead of instanceof for Effect Schema types",
33880
+ group: "effectNative",
33735
33881
  severity: "off",
33736
33882
  fixable: true,
33737
33883
  supportedEffect: ["v3", "v4"],
@@ -33797,6 +33943,7 @@ var layerMergeAllWithDependencies = createDiagnostic({
33797
33943
  name: "layerMergeAllWithDependencies",
33798
33944
  code: 37,
33799
33945
  description: "Detects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires",
33946
+ group: "antipattern",
33800
33947
  severity: "warning",
33801
33948
  fixable: true,
33802
33949
  supportedEffect: ["v3", "v4"],
@@ -33912,6 +34059,7 @@ var leakingRequirements = createDiagnostic({
33912
34059
  name: "leakingRequirements",
33913
34060
  code: 8,
33914
34061
  description: "Detects implementation services leaked in service methods",
34062
+ group: "antipattern",
33915
34063
  severity: "suggestion",
33916
34064
  fixable: false,
33917
34065
  supportedEffect: ["v3", "v4"],
@@ -33921,6 +34069,24 @@ var leakingRequirements = createDiagnostic({
33921
34069
  const typeCheckerUtils = yield* service2(TypeCheckerUtils);
33922
34070
  const typeParser = yield* service2(TypeParser);
33923
34071
  const tsUtils = yield* service2(TypeScriptUtils);
34072
+ const isExpectedLeakingServiceSuppressed = (leakedServiceName, startNode) => {
34073
+ const sourceFile2 = tsUtils.getSourceFileOfNode(startNode);
34074
+ if (!sourceFile2) return false;
34075
+ return !!ts.findAncestor(startNode, (current) => {
34076
+ const ranges = ts.getLeadingCommentRanges(sourceFile2.text, current.pos) ?? [];
34077
+ const isSuppressed = ranges.some((range2) => {
34078
+ const commentText = sourceFile2.text.slice(range2.pos, range2.end);
34079
+ return commentText.split("\n").filter((line) => line.includes("@effect-expect-leaking")).some((line) => {
34080
+ const markerIndex = line.indexOf("@effect-expect-leaking");
34081
+ return markerIndex !== -1 && line.slice(markerIndex + "@effect-expect-leaking".length).includes(leakedServiceName);
34082
+ });
34083
+ });
34084
+ if (isSuppressed) {
34085
+ return true;
34086
+ }
34087
+ return ts.isClassDeclaration(current) || ts.isVariableStatement(current) || ts.isExpressionStatement(current) || ts.isStatement(current) ? "quit" : false;
34088
+ });
34089
+ };
33924
34090
  const parseLeakedRequirements = cachedBy(
33925
34091
  fn3("leakingServices.checkServiceLeaking")(
33926
34092
  function* (service3, atLocation) {
@@ -34002,8 +34168,12 @@ var leakingRequirements = createDiagnostic({
34002
34168
  (_, service3) => service3
34003
34169
  );
34004
34170
  function reportLeakingRequirements(node, requirements) {
34005
- if (requirements.length === 0) return;
34006
- const requirementsStr = requirements.map((_) => typeChecker.typeToString(_)).join(" | ");
34171
+ const filteredRequirements = requirements.filter(
34172
+ (requirement) => !isExpectedLeakingServiceSuppressed(typeChecker.typeToString(requirement), node)
34173
+ );
34174
+ if (filteredRequirements.length === 0) return;
34175
+ const requirementsStr = filteredRequirements.map((_) => typeChecker.typeToString(_)).join(" | ");
34176
+ const firstStr = typeChecker.typeToString(filteredRequirements[0]);
34007
34177
  report({
34008
34178
  location: node,
34009
34179
  messageText: `Methods of this Service require \`${requirementsStr}\` from every caller.
@@ -34012,7 +34182,7 @@ This leaks implementation details into the service's public type \u2014 callers
34012
34182
 
34013
34183
  Resolve these dependencies at Layer creation and provide them to each method, so the service's type reflects its purpose, not its implementation.
34014
34184
 
34015
- To 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 \`${typeChecker.typeToString(requirements[0])}\` interface), not to this service.
34185
+ To 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 \`${firstStr}\` interface), or to this service by adding a \`@effect-expect-leaking ${firstStr}\` JSDoc.
34016
34186
 
34017
34187
  More info and examples at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage`,
34018
34188
  fixes: []
@@ -34068,6 +34238,7 @@ var missedPipeableOpportunity = createDiagnostic({
34068
34238
  name: "missedPipeableOpportunity",
34069
34239
  code: 26,
34070
34240
  description: "Enforces the use of pipeable style for nested function calls",
34241
+ group: "style",
34071
34242
  severity: "off",
34072
34243
  fixable: true,
34073
34244
  supportedEffect: ["v3", "v4"],
@@ -34250,6 +34421,7 @@ var missingEffectContext = createDiagnostic({
34250
34421
  name: "missingEffectContext",
34251
34422
  code: 1,
34252
34423
  description: "Reports missing service requirements in Effect context channel",
34424
+ group: "correctness",
34253
34425
  severity: "error",
34254
34426
  fixable: false,
34255
34427
  supportedEffect: ["v3", "v4"],
@@ -34301,6 +34473,7 @@ var missingEffectError = createDiagnostic({
34301
34473
  name: "missingEffectError",
34302
34474
  code: 1,
34303
34475
  description: "Reports missing error types in Effect error channel",
34476
+ group: "correctness",
34304
34477
  severity: "error",
34305
34478
  fixable: true,
34306
34479
  supportedEffect: ["v3", "v4"],
@@ -34444,6 +34617,7 @@ var missingEffectServiceDependency = createDiagnostic({
34444
34617
  name: "missingEffectServiceDependency",
34445
34618
  code: 22,
34446
34619
  description: "Checks that Effect.Service dependencies satisfy all required layer inputs",
34620
+ group: "style",
34447
34621
  severity: "off",
34448
34622
  fixable: false,
34449
34623
  supportedEffect: ["v3"],
@@ -34540,6 +34714,7 @@ var missingLayerContext = createDiagnostic({
34540
34714
  name: "missingLayerContext",
34541
34715
  code: 38,
34542
34716
  description: "Reports missing service requirements in Layer context channel",
34717
+ group: "correctness",
34543
34718
  severity: "error",
34544
34719
  fixable: false,
34545
34720
  supportedEffect: ["v3", "v4"],
@@ -34591,6 +34766,7 @@ var missingReturnYieldStar = createDiagnostic({
34591
34766
  name: "missingReturnYieldStar",
34592
34767
  code: 7,
34593
34768
  description: "Suggests using 'return yield*' for Effects with never success for better type narrowing",
34769
+ group: "correctness",
34594
34770
  severity: "error",
34595
34771
  fixable: true,
34596
34772
  supportedEffect: ["v3", "v4"],
@@ -34643,6 +34819,7 @@ var missingStarInYieldEffectGen = createDiagnostic({
34643
34819
  name: "missingStarInYieldEffectGen",
34644
34820
  code: 4,
34645
34821
  description: "Enforces using 'yield*' instead of 'yield' when yielding Effects in generators",
34822
+ group: "correctness",
34646
34823
  severity: "error",
34647
34824
  fixable: true,
34648
34825
  supportedEffect: ["v3", "v4"],
@@ -34720,6 +34897,7 @@ var multipleEffectProvide = createDiagnostic({
34720
34897
  name: "multipleEffectProvide",
34721
34898
  code: 18,
34722
34899
  description: "Warns against chaining Effect.provide calls which can cause service lifecycle issues",
34900
+ group: "antipattern",
34723
34901
  severity: "warning",
34724
34902
  fixable: true,
34725
34903
  supportedEffect: ["v3", "v4"],
@@ -34822,7 +35000,11 @@ var moduleAlternativesV3 = /* @__PURE__ */ new Map([
34822
35000
  ["path/win32", { alternative: "Path", module: "path", package: "@effect/platform" }],
34823
35001
  ["node:path/win32", { alternative: "Path", module: "path", package: "@effect/platform" }],
34824
35002
  ["child_process", { alternative: "CommandExecutor", module: "child_process", package: "@effect/platform" }],
34825
- ["node:child_process", { alternative: "CommandExecutor", module: "child_process", package: "@effect/platform" }]
35003
+ ["node:child_process", { alternative: "CommandExecutor", module: "child_process", package: "@effect/platform" }],
35004
+ ["http", { alternative: "HttpClient", module: "http", package: "@effect/platform" }],
35005
+ ["node:http", { alternative: "HttpClient", module: "http", package: "@effect/platform" }],
35006
+ ["https", { alternative: "HttpClient", module: "https", package: "@effect/platform" }],
35007
+ ["node:https", { alternative: "HttpClient", module: "https", package: "@effect/platform" }]
34826
35008
  ]);
34827
35009
  var moduleAlternativesV4 = /* @__PURE__ */ new Map([
34828
35010
  ["fs", { alternative: "FileSystem", module: "fs", package: "effect" }],
@@ -34836,12 +35018,17 @@ var moduleAlternativesV4 = /* @__PURE__ */ new Map([
34836
35018
  ["path/win32", { alternative: "Path", module: "path", package: "effect" }],
34837
35019
  ["node:path/win32", { alternative: "Path", module: "path", package: "effect" }],
34838
35020
  ["child_process", { alternative: "ChildProcess", module: "child_process", package: "effect" }],
34839
- ["node:child_process", { alternative: "ChildProcess", module: "child_process", package: "effect" }]
35021
+ ["node:child_process", { alternative: "ChildProcess", module: "child_process", package: "effect" }],
35022
+ ["http", { alternative: "HttpClient", module: "http", package: "effect/unstable/http" }],
35023
+ ["node:http", { alternative: "HttpClient", module: "http", package: "effect/unstable/http" }],
35024
+ ["https", { alternative: "HttpClient", module: "https", package: "effect/unstable/http" }],
35025
+ ["node:https", { alternative: "HttpClient", module: "https", package: "effect/unstable/http" }]
34840
35026
  ]);
34841
35027
  var nodeBuiltinImport = createDiagnostic({
34842
35028
  name: "nodeBuiltinImport",
34843
35029
  code: 52,
34844
35030
  description: "Warns when importing Node.js built-in modules that have Effect-native counterparts",
35031
+ group: "effectNative",
34845
35032
  severity: "off",
34846
35033
  fixable: false,
34847
35034
  supportedEffect: ["v3", "v4"],
@@ -34885,6 +35072,7 @@ var nonObjectEffectServiceType = createDiagnostic({
34885
35072
  name: "nonObjectEffectServiceType",
34886
35073
  code: 24,
34887
35074
  description: "Ensures Effect.Service types are objects, not primitives",
35075
+ group: "correctness",
34888
35076
  severity: "error",
34889
35077
  fixable: false,
34890
35078
  supportedEffect: ["v3"],
@@ -35669,6 +35857,7 @@ var outdatedApi = createDiagnostic({
35669
35857
  name: "outdatedApi",
35670
35858
  code: 48,
35671
35859
  description: "Detects usage of APIs that have been removed or renamed in Effect v4",
35860
+ group: "correctness",
35672
35861
  severity: "warning",
35673
35862
  fixable: false,
35674
35863
  supportedEffect: ["v4"],
@@ -35738,6 +35927,7 @@ var outdatedEffectCodegen = createDiagnostic({
35738
35927
  name: "outdatedEffectCodegen",
35739
35928
  code: 19,
35740
35929
  description: "Detects when generated code is outdated and needs to be regenerated",
35930
+ group: "correctness",
35741
35931
  severity: "warning",
35742
35932
  fixable: true,
35743
35933
  supportedEffect: ["v3", "v4"],
@@ -35786,6 +35976,7 @@ var overriddenSchemaConstructor = createDiagnostic({
35786
35976
  name: "overriddenSchemaConstructor",
35787
35977
  code: 30,
35788
35978
  description: "Prevents overriding constructors in Schema classes which breaks decoding behavior",
35979
+ group: "correctness",
35789
35980
  severity: "error",
35790
35981
  fixable: true,
35791
35982
  supportedEffect: ["v3", "v4"],
@@ -35925,6 +36116,7 @@ var preferSchemaOverJson = createDiagnostic({
35925
36116
  name: "preferSchemaOverJson",
35926
36117
  code: 44,
35927
36118
  description: "Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify which may throw",
36119
+ group: "effectNative",
35928
36120
  severity: "suggestion",
35929
36121
  fixable: false,
35930
36122
  supportedEffect: ["v3", "v4"],
@@ -36037,6 +36229,7 @@ var redundantSchemaTagIdentifier = createDiagnostic({
36037
36229
  name: "redundantSchemaTagIdentifier",
36038
36230
  code: 42,
36039
36231
  description: "Suggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest",
36232
+ group: "style",
36040
36233
  severity: "suggestion",
36041
36234
  fixable: true,
36042
36235
  supportedEffect: ["v3", "v4"],
@@ -36089,6 +36282,7 @@ var returnEffectInGen = createDiagnostic({
36089
36282
  name: "returnEffectInGen",
36090
36283
  code: 11,
36091
36284
  description: "Warns when returning an Effect in a generator causes nested Effect<Effect<...>>",
36285
+ group: "antipattern",
36092
36286
  severity: "suggestion",
36093
36287
  fixable: true,
36094
36288
  supportedEffect: ["v3", "v4"],
@@ -36160,6 +36354,7 @@ var runEffectInsideEffect = createDiagnostic({
36160
36354
  name: "runEffectInsideEffect",
36161
36355
  code: 32,
36162
36356
  description: "Suggests using Runtime methods instead of Effect.run* inside Effect contexts",
36357
+ group: "antipattern",
36163
36358
  severity: "suggestion",
36164
36359
  fixable: true,
36165
36360
  supportedEffect: ["v3"],
@@ -36286,6 +36481,7 @@ var schemaStructWithTag = createDiagnostic({
36286
36481
  name: "schemaStructWithTag",
36287
36482
  code: 34,
36288
36483
  description: "Suggests using Schema.TaggedStruct instead of Schema.Struct with _tag field",
36484
+ group: "style",
36289
36485
  severity: "suggestion",
36290
36486
  fixable: true,
36291
36487
  supportedEffect: ["v3", "v4"],
@@ -36380,9 +36576,10 @@ var schemaSyncInEffect = createDiagnostic({
36380
36576
  name: "schemaSyncInEffect",
36381
36577
  code: 43,
36382
36578
  description: "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
36579
+ group: "antipattern",
36383
36580
  severity: "suggestion",
36384
36581
  fixable: false,
36385
- supportedEffect: ["v3", "v4"],
36582
+ supportedEffect: ["v3"],
36386
36583
  apply: fn3("schemaSyncInEffect.apply")(function* (sourceFile, report) {
36387
36584
  const ts = yield* service2(TypeScriptApi);
36388
36585
  const typeParser = yield* service2(TypeParser);
@@ -36431,6 +36628,7 @@ var schemaUnionOfLiterals = createDiagnostic({
36431
36628
  name: "schemaUnionOfLiterals",
36432
36629
  code: 33,
36433
36630
  description: "Simplifies Schema.Union of multiple Schema.Literal calls into single Schema.Literal",
36631
+ group: "style",
36434
36632
  severity: "off",
36435
36633
  fixable: true,
36436
36634
  supportedEffect: ["v3"],
@@ -36508,6 +36706,7 @@ var scopeInLayerEffect = createDiagnostic({
36508
36706
  name: "scopeInLayerEffect",
36509
36707
  code: 13,
36510
36708
  description: "Suggests using Layer.scoped instead of Layer.effect when Scope is in requirements",
36709
+ group: "antipattern",
36511
36710
  severity: "warning",
36512
36711
  fixable: true,
36513
36712
  supportedEffect: ["v3"],
@@ -36605,6 +36804,7 @@ var serviceNotAsClass = createDiagnostic({
36605
36804
  name: "serviceNotAsClass",
36606
36805
  code: 51,
36607
36806
  description: "Warns when ServiceMap.Service is used as a variable instead of a class declaration",
36807
+ group: "style",
36608
36808
  severity: "off",
36609
36809
  fixable: true,
36610
36810
  supportedEffect: ["v4"],
@@ -36682,6 +36882,7 @@ var strictBooleanExpressions = createDiagnostic({
36682
36882
  name: "strictBooleanExpressions",
36683
36883
  code: 17,
36684
36884
  description: "Enforces boolean types in conditional expressions for type safety",
36885
+ group: "style",
36685
36886
  severity: "off",
36686
36887
  fixable: false,
36687
36888
  supportedEffect: ["v3", "v4"],
@@ -36755,6 +36956,7 @@ var strictEffectProvide = createDiagnostic({
36755
36956
  name: "strictEffectProvide",
36756
36957
  code: 27,
36757
36958
  description: "Warns when using Effect.provide with layers outside of application entry points",
36959
+ group: "antipattern",
36758
36960
  severity: "off",
36759
36961
  fixable: false,
36760
36962
  supportedEffect: ["v3", "v4"],
@@ -36808,6 +37010,7 @@ var tryCatchInEffectGen = createDiagnostic({
36808
37010
  name: "tryCatchInEffectGen",
36809
37011
  code: 15,
36810
37012
  description: "Discourages try/catch in Effect generators in favor of Effect error handling",
37013
+ group: "antipattern",
36811
37014
  severity: "suggestion",
36812
37015
  fixable: false,
36813
37016
  supportedEffect: ["v3", "v4"],
@@ -36867,6 +37070,7 @@ var unknownInEffectCatch = createDiagnostic({
36867
37070
  name: "unknownInEffectCatch",
36868
37071
  code: 31,
36869
37072
  description: "Warns when catch callbacks return unknown instead of typed errors",
37073
+ group: "antipattern",
36870
37074
  severity: "warning",
36871
37075
  fixable: false,
36872
37076
  supportedEffect: ["v3", "v4"],
@@ -36930,6 +37134,7 @@ var unnecessaryEffectGen = createDiagnostic({
36930
37134
  name: "unnecessaryEffectGen",
36931
37135
  code: 5,
36932
37136
  description: "Suggests removing Effect.gen when it contains only a single return statement",
37137
+ group: "style",
36933
37138
  severity: "suggestion",
36934
37139
  fixable: true,
36935
37140
  supportedEffect: ["v3", "v4"],
@@ -36976,6 +37181,7 @@ var unnecessaryFailYieldableError = createDiagnostic({
36976
37181
  name: "unnecessaryFailYieldableError",
36977
37182
  code: 29,
36978
37183
  description: "Suggests yielding yieldable errors directly instead of wrapping with Effect.fail",
37184
+ group: "style",
36979
37185
  severity: "suggestion",
36980
37186
  fixable: true,
36981
37187
  supportedEffect: ["v3", "v4"],
@@ -37037,6 +37243,7 @@ var unnecessaryPipe = createDiagnostic({
37037
37243
  name: "unnecessaryPipe",
37038
37244
  code: 9,
37039
37245
  description: "Removes pipe calls with no arguments",
37246
+ group: "style",
37040
37247
  severity: "suggestion",
37041
37248
  fixable: true,
37042
37249
  supportedEffect: ["v3", "v4"],
@@ -37085,6 +37292,7 @@ var unnecessaryPipeChain = createDiagnostic({
37085
37292
  name: "unnecessaryPipeChain",
37086
37293
  code: 16,
37087
37294
  description: "Simplifies chained pipe calls into a single pipe call",
37295
+ group: "style",
37088
37296
  severity: "suggestion",
37089
37297
  fixable: true,
37090
37298
  supportedEffect: ["v3", "v4"],
@@ -37162,6 +37370,7 @@ var unsupportedServiceAccessors = createDiagnostic({
37162
37370
  name: "unsupportedServiceAccessors",
37163
37371
  code: 21,
37164
37372
  description: "Warns about service accessors that need codegen due to generic/complex signatures",
37373
+ group: "correctness",
37165
37374
  severity: "warning",
37166
37375
  fixable: true,
37167
37376
  supportedEffect: ["v3", "v4"],
@@ -37225,6 +37434,7 @@ var diagnostics = [
37225
37434
  catchUnfailableEffect,
37226
37435
  classSelfMismatch,
37227
37436
  duplicatePackage,
37437
+ effectFnImplicitAny,
37228
37438
  effectGenUsesAdapter,
37229
37439
  missingEffectContext,
37230
37440
  missingEffectError,
@@ -37239,6 +37449,7 @@ var diagnostics = [
37239
37449
  leakingRequirements,
37240
37450
  unnecessaryPipe,
37241
37451
  genericEffectServices,
37452
+ globalFetch,
37242
37453
  returnEffectInGen,
37243
37454
  tryCatchInEffectGen,
37244
37455
  importFromBarrel,
@@ -37307,6 +37518,8 @@ var formatDiagnosticForJson = (diagnostic, tsInstance) => {
37307
37518
  const diagnosticName = Object.values(diagnostics).find((_) => _.code === diagnostic.code)?.name ?? `effect(${diagnostic.code})`;
37308
37519
  return {
37309
37520
  file: diagnostic.file.fileName,
37521
+ start: diagnostic.start,
37522
+ length: diagnostic.length ?? 0,
37310
37523
  line: line + 1,
37311
37524
  column: character + 1,
37312
37525
  endLine: endLine + 1,
@@ -38201,13 +38414,6 @@ var GREEN = "\x1B[0;32m";
38201
38414
  var YELLOW = "\x1B[0;33m";
38202
38415
  var BLUE = "\x1B[0;34m";
38203
38416
  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
38417
  var ansi = (text2, code) => `${code}${text2}${RESET}`;
38212
38418
  var ERASE_LINE = "\x1B[2K";
38213
38419
  var CURSOR_LEFT = "\r";
@@ -38215,6 +38421,11 @@ var CURSOR_HIDE = "\x1B[?25l";
38215
38421
  var CURSOR_SHOW = "\x1B[?25h";
38216
38422
  var CURSOR_TO_0 = "\x1B[G";
38217
38423
  var BEEP = "\x07";
38424
+ var ITALIC = "\x1B[0;3m";
38425
+ var UNDERLINE = "\x1B[0;4m";
38426
+ var ANSI_ESCAPE_REGEX = new RegExp(String.raw`\u001b\[[0-?]*[ -/]*[@-~]`, "g");
38427
+ var stripAnsi2 = (text2) => text2.replace(ANSI_ESCAPE_REGEX, "");
38428
+ var visibleLength = (text2) => stripAnsi2(text2).length;
38218
38429
 
38219
38430
  // src/cli/overview.ts
38220
38431
  var import_project_service3 = __toESM(require_dist3());
@@ -40178,9 +40389,100 @@ var computeTsConfigChanges = (current, target, lspVersion) => {
40178
40389
  if (!rootObj) {
40179
40390
  return emptyFileChangesResult();
40180
40391
  }
40392
+ const buildPluginObject = (severities) => {
40393
+ const nameProperty = ts.factory.createPropertyAssignment(
40394
+ ts.factory.createStringLiteral("name"),
40395
+ ts.factory.createStringLiteral("@effect/language-service")
40396
+ );
40397
+ return match(severities, {
40398
+ onNone: () => {
40399
+ return ts.factory.createObjectLiteralExpression([nameProperty], false);
40400
+ },
40401
+ onSome: (sevs) => {
40402
+ const severityProperties = Object.entries(sevs).map(
40403
+ ([name, severity]) => ts.factory.createPropertyAssignment(
40404
+ ts.factory.createStringLiteral(name),
40405
+ ts.factory.createStringLiteral(severity)
40406
+ )
40407
+ );
40408
+ const diagnosticSeverityProperty = ts.factory.createPropertyAssignment(
40409
+ ts.factory.createStringLiteral("diagnosticSeverity"),
40410
+ ts.factory.createObjectLiteralExpression(severityProperties, true)
40411
+ );
40412
+ return ts.factory.createObjectLiteralExpression(
40413
+ [nameProperty, diagnosticSeverityProperty],
40414
+ true
40415
+ );
40416
+ }
40417
+ });
40418
+ };
40419
+ const schemaPropertyAssignment = ts.factory.createPropertyAssignment(
40420
+ ts.factory.createStringLiteral("$schema"),
40421
+ ts.factory.createStringLiteral(TSCONFIG_SCHEMA_URL)
40422
+ );
40181
40423
  const compilerOptionsProperty = findPropertyInObject(ts, rootObj, "compilerOptions");
40182
40424
  if (!compilerOptionsProperty) {
40183
- return emptyFileChangesResult();
40425
+ if (isNone2(lspVersion)) {
40426
+ return emptyFileChangesResult();
40427
+ }
40428
+ const textChanges2 = ts.textChanges;
40429
+ const host2 = createMinimalHost(ts);
40430
+ const formatOptions2 = { indentSize: 2, tabSize: 2 };
40431
+ const formatContext2 = ts.formatting.getFormatContext(formatOptions2, host2);
40432
+ const preferences2 = {};
40433
+ const fileChanges2 = textChanges2.ChangeTracker.with(
40434
+ { host: host2, formatContext: formatContext2, preferences: preferences2 },
40435
+ (tracker) => {
40436
+ const schemaProperty = findPropertyInObject(ts, rootObj, "$schema");
40437
+ const shouldAddSchema = !schemaProperty;
40438
+ const shouldUpdateSchema = !!schemaProperty && (!ts.isStringLiteral(schemaProperty.initializer) || schemaProperty.initializer.text !== TSCONFIG_SCHEMA_URL);
40439
+ if (shouldAddSchema) {
40440
+ descriptions.push("Add $schema to tsconfig");
40441
+ } else if (shouldUpdateSchema) {
40442
+ descriptions.push("Update $schema in tsconfig");
40443
+ }
40444
+ descriptions.push("Add compilerOptions with @effect/language-service plugin");
40445
+ const compilerOptionsAssignment = ts.factory.createPropertyAssignment(
40446
+ ts.factory.createStringLiteral("compilerOptions"),
40447
+ ts.factory.createObjectLiteralExpression([
40448
+ ts.factory.createPropertyAssignment(
40449
+ ts.factory.createStringLiteral("plugins"),
40450
+ ts.factory.createArrayLiteralExpression([buildPluginObject(target.diagnosticSeverities)], true)
40451
+ )
40452
+ ], true)
40453
+ );
40454
+ const nextProperties = rootObj.properties.flatMap((property) => {
40455
+ if (schemaProperty && property === schemaProperty) {
40456
+ return [schemaPropertyAssignment];
40457
+ }
40458
+ return [property];
40459
+ });
40460
+ if (shouldAddSchema) {
40461
+ nextProperties.push(schemaPropertyAssignment);
40462
+ }
40463
+ nextProperties.push(compilerOptionsAssignment);
40464
+ tracker.replaceNode(
40465
+ current.sourceFile,
40466
+ rootObj,
40467
+ ts.factory.createObjectLiteralExpression(nextProperties, true)
40468
+ );
40469
+ }
40470
+ );
40471
+ const fileChange2 = fileChanges2.find((fc) => fc.fileName === current.path);
40472
+ const changes2 = fileChange2 ? fileChange2.textChanges : [];
40473
+ if (changes2.length === 0) {
40474
+ return { codeActions: [], messages };
40475
+ }
40476
+ return {
40477
+ codeActions: [{
40478
+ description: descriptions.join("; "),
40479
+ changes: [{
40480
+ fileName: current.path,
40481
+ textChanges: changes2
40482
+ }]
40483
+ }],
40484
+ messages
40485
+ };
40184
40486
  }
40185
40487
  if (!ts.isObjectLiteralExpression(compilerOptionsProperty.initializer)) {
40186
40488
  return emptyFileChangesResult();
@@ -40196,10 +40498,6 @@ var computeTsConfigChanges = (current, target, lspVersion) => {
40196
40498
  (tracker) => {
40197
40499
  const schemaProperty = findPropertyInObject(ts, rootObj, "$schema");
40198
40500
  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
40501
  if (isNone2(lspVersion)) {
40204
40502
  if (schemaProperty) {
40205
40503
  descriptions.push("Remove $schema from tsconfig");
@@ -40229,33 +40527,6 @@ var computeTsConfigChanges = (current, target, lspVersion) => {
40229
40527
  descriptions.push("Update $schema in tsconfig");
40230
40528
  tracker.replaceNode(current.sourceFile, schemaProperty.initializer, schemaPropertyAssignment.initializer);
40231
40529
  }
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
40530
  const pluginObject = buildPluginObject(target.diagnosticSeverities);
40260
40531
  if (!pluginsProperty) {
40261
40532
  descriptions.push("Add plugins array with @effect/language-service plugin");
@@ -40468,244 +40739,1677 @@ var FileReadError = class extends TaggedError2("FileReadError") {
40468
40739
  }
40469
40740
  };
40470
40741
 
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 }
40742
+ // src/metadata.json
40743
+ var metadata_default = {
40744
+ groups: [
40745
+ {
40746
+ id: "correctness",
40747
+ name: "Correctness",
40748
+ description: "Wrong, unsafe, or structurally invalid code patterns."
40749
+ },
40750
+ {
40751
+ id: "antipattern",
40752
+ name: "Anti-pattern",
40753
+ description: "Discouraged patterns that often lead to bugs or confusing behavior."
40754
+ },
40755
+ {
40756
+ id: "effectNative",
40757
+ name: "Effect-native",
40758
+ description: "Prefer Effect-native APIs and abstractions when available."
40759
+ },
40760
+ {
40761
+ id: "style",
40762
+ name: "Style",
40763
+ description: "Cleanup, consistency, and idiomatic Effect code."
40644
40764
  }
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);
40654
- }
40655
- case "j":
40656
- case "down": {
40657
- return processCursorDown(state, totalCount);
40765
+ ],
40766
+ rules: [
40767
+ {
40768
+ name: "anyUnknownInErrorContext",
40769
+ group: "correctness",
40770
+ description: "Detects 'any' or 'unknown' types in Effect error or requirements channels",
40771
+ defaultSeverity: "off",
40772
+ fixable: false,
40773
+ supportedEffect: [
40774
+ "v3",
40775
+ "v4"
40776
+ ],
40777
+ preview: {
40778
+ 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',
40779
+ diagnostics: [
40780
+ {
40781
+ start: 46,
40782
+ end: 53,
40783
+ 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)"
40784
+ },
40785
+ {
40786
+ start: 90,
40787
+ end: 116,
40788
+ text: "This has unknown in the requirements channel which is not recommended.\nOnly service identifiers should appear in the requirements channel. effect(anyUnknownInErrorContext)"
40789
+ },
40790
+ {
40791
+ start: 133,
40792
+ end: 157,
40793
+ 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)"
40794
+ }
40795
+ ]
40658
40796
  }
40659
- case "left": {
40660
- return processSeverityChange(state, options, "left");
40797
+ },
40798
+ {
40799
+ name: "classSelfMismatch",
40800
+ group: "correctness",
40801
+ description: "Ensures Self type parameter matches the class name in Service/Tag/Schema classes",
40802
+ defaultSeverity: "error",
40803
+ fixable: true,
40804
+ supportedEffect: [
40805
+ "v3",
40806
+ "v4"
40807
+ ],
40808
+ preview: {
40809
+ 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',
40810
+ diagnostics: [
40811
+ {
40812
+ start: 154,
40813
+ end: 169,
40814
+ text: "Self type parameter should be 'InvalidContextTag' effect(classSelfMismatch)"
40815
+ }
40816
+ ]
40661
40817
  }
40662
- case "right": {
40663
- return processSeverityChange(state, options, "right");
40818
+ },
40819
+ {
40820
+ name: "duplicatePackage",
40821
+ group: "correctness",
40822
+ description: "Detects when multiple versions of the same Effect package are loaded",
40823
+ defaultSeverity: "warning",
40824
+ fixable: false,
40825
+ supportedEffect: [
40826
+ "v3",
40827
+ "v4"
40828
+ ],
40829
+ preview: {
40830
+ 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',
40831
+ diagnostics: []
40664
40832
  }
40665
- case "enter":
40666
- case "return": {
40667
- return succeed6(Action2.Submit({ value: state.severities }));
40833
+ },
40834
+ {
40835
+ name: "effectFnImplicitAny",
40836
+ group: "correctness",
40837
+ description: "Mirrors noImplicitAny for unannotated Effect.fn and Effect.fnUntraced callback parameters when no outer contextual function type exists",
40838
+ defaultSeverity: "error",
40839
+ fixable: false,
40840
+ supportedEffect: [
40841
+ "v3",
40842
+ "v4"
40843
+ ],
40844
+ preview: {
40845
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.fn("preview")((input) => Effect.succeed(input))\n',
40846
+ diagnostics: [
40847
+ {
40848
+ start: 86,
40849
+ end: 91,
40850
+ text: "Parameter 'input' implicitly has an 'any' type in Effect.fn/Effect.fnUntraced. Add an explicit type annotation or provide a contextual function type. effect(effectFnImplicitAny)"
40851
+ }
40852
+ ]
40668
40853
  }
40669
- default: {
40670
- return succeed6(Action2.Beep());
40854
+ },
40855
+ {
40856
+ name: "floatingEffect",
40857
+ group: "correctness",
40858
+ description: "Ensures Effects are yielded or assigned to variables, not left floating",
40859
+ defaultSeverity: "error",
40860
+ fixable: false,
40861
+ supportedEffect: [
40862
+ "v3",
40863
+ "v4"
40864
+ ],
40865
+ preview: {
40866
+ sourceText: 'import * as Effect from "effect/Effect"\n\nEffect.log("forgotten")\n',
40867
+ diagnostics: [
40868
+ {
40869
+ start: 41,
40870
+ end: 64,
40871
+ text: "Effect must be yielded or assigned to a variable. effect(floatingEffect)"
40872
+ }
40873
+ ]
40671
40874
  }
40672
- }
40673
- };
40875
+ },
40876
+ {
40877
+ name: "genericEffectServices",
40878
+ group: "correctness",
40879
+ description: "Prevents services with type parameters that cannot be discriminated at runtime",
40880
+ defaultSeverity: "warning",
40881
+ fixable: false,
40882
+ supportedEffect: [
40883
+ "v3",
40884
+ "v4"
40885
+ ],
40886
+ preview: {
40887
+ sourceText: 'import { Effect } from "effect"\n\nexport class Preview<_A> extends Effect.Service<Preview<any>>()("Preview", {\n succeed: {}\n}) {}\n',
40888
+ diagnostics: [
40889
+ {
40890
+ start: 46,
40891
+ end: 53,
40892
+ text: "Effect Services with type parameters are not supported because they cannot be properly discriminated at runtime, which may cause unexpected behavior. effect(genericEffectServices)"
40893
+ }
40894
+ ]
40895
+ }
40896
+ },
40897
+ {
40898
+ name: "missingEffectContext",
40899
+ group: "correctness",
40900
+ description: "Reports missing service requirements in Effect context channel",
40901
+ defaultSeverity: "error",
40902
+ fixable: false,
40903
+ supportedEffect: [
40904
+ "v3",
40905
+ "v4"
40906
+ ],
40907
+ preview: {
40908
+ 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',
40909
+ diagnostics: [
40910
+ {
40911
+ start: 160,
40912
+ end: 167,
40913
+ text: "Missing 'Db' in the expected Effect context. effect(missingEffectContext)"
40914
+ }
40915
+ ]
40916
+ }
40917
+ },
40918
+ {
40919
+ name: "missingEffectError",
40920
+ group: "correctness",
40921
+ description: "Reports missing error types in Effect error channel",
40922
+ defaultSeverity: "error",
40923
+ fixable: true,
40924
+ supportedEffect: [
40925
+ "v3",
40926
+ "v4"
40927
+ ],
40928
+ preview: {
40929
+ 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',
40930
+ diagnostics: [
40931
+ {
40932
+ start: 256,
40933
+ end: 262,
40934
+ text: "Missing 'Boom' in the expected Effect errors. effect(missingEffectError)"
40935
+ }
40936
+ ]
40937
+ }
40938
+ },
40939
+ {
40940
+ name: "missingLayerContext",
40941
+ group: "correctness",
40942
+ description: "Reports missing service requirements in Layer context channel",
40943
+ defaultSeverity: "error",
40944
+ fixable: false,
40945
+ supportedEffect: [
40946
+ "v3",
40947
+ "v4"
40948
+ ],
40949
+ preview: {
40950
+ 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',
40951
+ diagnostics: [
40952
+ {
40953
+ start: 259,
40954
+ end: 266,
40955
+ text: "Missing 'A' in the expected Layer context. effect(missingLayerContext)"
40956
+ }
40957
+ ]
40958
+ }
40959
+ },
40960
+ {
40961
+ name: "missingReturnYieldStar",
40962
+ group: "correctness",
40963
+ description: "Suggests using 'return yield*' for Effects with never success for better type narrowing",
40964
+ defaultSeverity: "error",
40965
+ fixable: true,
40966
+ supportedEffect: [
40967
+ "v3",
40968
+ "v4"
40969
+ ],
40970
+ preview: {
40971
+ 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',
40972
+ diagnostics: [
40973
+ {
40974
+ start: 121,
40975
+ end: 147,
40976
+ 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)"
40977
+ }
40978
+ ]
40979
+ }
40980
+ },
40981
+ {
40982
+ name: "missingStarInYieldEffectGen",
40983
+ group: "correctness",
40984
+ description: "Enforces using 'yield*' instead of 'yield' when yielding Effects in generators",
40985
+ defaultSeverity: "error",
40986
+ fixable: true,
40987
+ supportedEffect: [
40988
+ "v3",
40989
+ "v4"
40990
+ ],
40991
+ preview: {
40992
+ 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',
40993
+ diagnostics: [
40994
+ {
40995
+ start: 75,
40996
+ end: 83,
40997
+ text: "Seems like you used yield instead of yield* inside this Effect.gen. effect(missingStarInYieldEffectGen)"
40998
+ },
40999
+ {
41000
+ start: 105,
41001
+ end: 128,
41002
+ text: "When yielding Effects inside Effect.gen, you should use yield* instead of yield. effect(missingStarInYieldEffectGen)"
41003
+ }
41004
+ ]
41005
+ }
41006
+ },
41007
+ {
41008
+ name: "nonObjectEffectServiceType",
41009
+ group: "correctness",
41010
+ description: "Ensures Effect.Service types are objects, not primitives",
41011
+ defaultSeverity: "error",
41012
+ fixable: false,
41013
+ supportedEffect: [
41014
+ "v3"
41015
+ ],
41016
+ preview: {
41017
+ 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',
41018
+ diagnostics: [
41019
+ {
41020
+ start: 142,
41021
+ end: 149,
41022
+ 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)"
41023
+ }
41024
+ ]
41025
+ }
41026
+ },
41027
+ {
41028
+ name: "outdatedApi",
41029
+ group: "correctness",
41030
+ description: "Detects usage of APIs that have been removed or renamed in Effect v4",
41031
+ defaultSeverity: "warning",
41032
+ fixable: false,
41033
+ supportedEffect: [
41034
+ "v4"
41035
+ ],
41036
+ preview: {
41037
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.gen(function*() {\n // @ts-expect-error\n return yield* Effect.runtime()\n})\n',
41038
+ diagnostics: [
41039
+ {
41040
+ start: 0,
41041
+ end: 0,
41042
+ 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)"
41043
+ },
41044
+ {
41045
+ start: 126,
41046
+ end: 133,
41047
+ text: "runtime is an Effect v3 API, but the project is using Effect v4. effect(outdatedApi)"
41048
+ }
41049
+ ]
41050
+ }
41051
+ },
41052
+ {
41053
+ name: "outdatedEffectCodegen",
41054
+ group: "correctness",
41055
+ description: "Detects when generated code is outdated and needs to be regenerated",
41056
+ defaultSeverity: "warning",
41057
+ fixable: true,
41058
+ supportedEffect: [
41059
+ "v3",
41060
+ "v4"
41061
+ ],
41062
+ preview: {
41063
+ 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',
41064
+ diagnostics: [
41065
+ {
41066
+ start: 61,
41067
+ end: 76,
41068
+ text: "Codegen accessors result is outdated effect(outdatedEffectCodegen)"
41069
+ }
41070
+ ]
41071
+ }
41072
+ },
41073
+ {
41074
+ name: "overriddenSchemaConstructor",
41075
+ group: "correctness",
41076
+ description: "Prevents overriding constructors in Schema classes which breaks decoding behavior",
41077
+ defaultSeverity: "error",
41078
+ fixable: true,
41079
+ supportedEffect: [
41080
+ "v3",
41081
+ "v4"
41082
+ ],
41083
+ preview: {
41084
+ 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',
41085
+ diagnostics: [
41086
+ {
41087
+ start: 123,
41088
+ end: 185,
41089
+ 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)"
41090
+ }
41091
+ ]
41092
+ }
41093
+ },
41094
+ {
41095
+ name: "unsupportedServiceAccessors",
41096
+ group: "correctness",
41097
+ description: "Warns about service accessors that need codegen due to generic/complex signatures",
41098
+ defaultSeverity: "warning",
41099
+ fixable: true,
41100
+ supportedEffect: [
41101
+ "v3",
41102
+ "v4"
41103
+ ],
41104
+ preview: {
41105
+ 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',
41106
+ diagnostics: [
41107
+ {
41108
+ start: 54,
41109
+ end: 61,
41110
+ 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)"
41111
+ }
41112
+ ]
41113
+ }
41114
+ },
41115
+ {
41116
+ name: "catchUnfailableEffect",
41117
+ group: "antipattern",
41118
+ description: "Warns when using error handling on Effects that never fail (error type is 'never')",
41119
+ defaultSeverity: "suggestion",
41120
+ fixable: false,
41121
+ supportedEffect: [
41122
+ "v3",
41123
+ "v4"
41124
+ ],
41125
+ preview: {
41126
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.succeed(1).pipe(\n Effect.catch(() => Effect.succeed(0))\n)\n',
41127
+ diagnostics: [
41128
+ {
41129
+ start: 82,
41130
+ end: 94,
41131
+ text: "Looks like the previous effect never fails, so probably this error handling will never be triggered. effect(catchUnfailableEffect)"
41132
+ }
41133
+ ]
41134
+ }
41135
+ },
41136
+ {
41137
+ name: "effectFnIife",
41138
+ group: "antipattern",
41139
+ description: "Effect.fn or Effect.fnUntraced is called as an IIFE (Immediately Invoked Function Expression). Use Effect.gen instead.",
41140
+ defaultSeverity: "warning",
41141
+ fixable: true,
41142
+ supportedEffect: [
41143
+ "v3",
41144
+ "v4"
41145
+ ],
41146
+ preview: {
41147
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.fn("preview")(function*() {\n return yield* Effect.succeed(1)\n})()\n',
41148
+ diagnostics: [
41149
+ {
41150
+ start: 64,
41151
+ end: 137,
41152
+ 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)`
41153
+ }
41154
+ ]
41155
+ }
41156
+ },
41157
+ {
41158
+ name: "effectGenUsesAdapter",
41159
+ group: "antipattern",
41160
+ description: "Warns when using the deprecated adapter parameter in Effect.gen",
41161
+ defaultSeverity: "warning",
41162
+ fixable: false,
41163
+ supportedEffect: [
41164
+ "v3",
41165
+ "v4"
41166
+ ],
41167
+ preview: {
41168
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*(_) {\n return yield* Effect.succeed(1)\n})\n',
41169
+ diagnostics: [
41170
+ {
41171
+ start: 85,
41172
+ end: 86,
41173
+ text: "The adapter of Effect.gen is not required anymore, it is now just an alias of pipe. effect(effectGenUsesAdapter)"
41174
+ }
41175
+ ]
41176
+ }
41177
+ },
41178
+ {
41179
+ name: "effectInFailure",
41180
+ group: "antipattern",
41181
+ description: "Warns when an Effect is used inside an Effect failure channel",
41182
+ defaultSeverity: "warning",
41183
+ fixable: false,
41184
+ supportedEffect: [
41185
+ "v3",
41186
+ "v4"
41187
+ ],
41188
+ preview: {
41189
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.try({\n try: () => JSON.parse("{"),\n catch: (error) => Effect.logError(error)\n})\n',
41190
+ diagnostics: [
41191
+ {
41192
+ start: 46,
41193
+ end: 53,
41194
+ 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)"
41195
+ },
41196
+ {
41197
+ start: 56,
41198
+ end: 144,
41199
+ 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)"
41200
+ }
41201
+ ]
41202
+ }
41203
+ },
41204
+ {
41205
+ name: "effectInVoidSuccess",
41206
+ group: "antipattern",
41207
+ description: "Detects nested Effects in void success channels that may cause unexecuted effects",
41208
+ defaultSeverity: "warning",
41209
+ fixable: false,
41210
+ supportedEffect: [
41211
+ "v3",
41212
+ "v4"
41213
+ ],
41214
+ preview: {
41215
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview: Effect.Effect<void> = Effect.succeed(\n Effect.log("nested")\n)\n',
41216
+ diagnostics: [
41217
+ {
41218
+ start: 54,
41219
+ end: 61,
41220
+ 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)"
41221
+ }
41222
+ ]
41223
+ }
41224
+ },
41225
+ {
41226
+ name: "globalErrorInEffectCatch",
41227
+ group: "antipattern",
41228
+ description: "Warns when catch callbacks return global Error type instead of typed errors",
41229
+ defaultSeverity: "warning",
41230
+ fixable: false,
41231
+ supportedEffect: [
41232
+ "v3",
41233
+ "v4"
41234
+ ],
41235
+ preview: {
41236
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.tryPromise({\n try: async () => 1,\n catch: (error) => new Error(String(error))\n})\n',
41237
+ diagnostics: [
41238
+ {
41239
+ start: 56,
41240
+ end: 73,
41241
+ 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)"
41242
+ }
41243
+ ]
41244
+ }
41245
+ },
41246
+ {
41247
+ name: "globalErrorInEffectFailure",
41248
+ group: "antipattern",
41249
+ description: "Warns when the global Error type is used in an Effect failure channel",
41250
+ defaultSeverity: "warning",
41251
+ fixable: false,
41252
+ supportedEffect: [
41253
+ "v3",
41254
+ "v4"
41255
+ ],
41256
+ preview: {
41257
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.fail(new Error("boom"))\n',
41258
+ diagnostics: [
41259
+ {
41260
+ start: 68,
41261
+ end: 85,
41262
+ 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)"
41263
+ }
41264
+ ]
41265
+ }
41266
+ },
41267
+ {
41268
+ name: "layerMergeAllWithDependencies",
41269
+ group: "antipattern",
41270
+ description: "Detects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires",
41271
+ defaultSeverity: "warning",
41272
+ fixable: true,
41273
+ supportedEffect: [
41274
+ "v3",
41275
+ "v4"
41276
+ ],
41277
+ preview: {
41278
+ 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',
41279
+ diagnostics: [
41280
+ {
41281
+ start: 355,
41282
+ end: 364,
41283
+ 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)"
41284
+ }
41285
+ ]
41286
+ }
41287
+ },
41288
+ {
41289
+ name: "leakingRequirements",
41290
+ group: "antipattern",
41291
+ description: "Detects implementation services leaked in service methods",
41292
+ defaultSeverity: "suggestion",
41293
+ fixable: false,
41294
+ supportedEffect: [
41295
+ "v3",
41296
+ "v4"
41297
+ ],
41298
+ preview: {
41299
+ 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',
41300
+ diagnostics: [
41301
+ {
41302
+ start: 212,
41303
+ end: 217,
41304
+ 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), or to this service by adding a `@effect-expect-leaking FileSystem` JSDoc.\n\nMore info and examples at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage effect(leakingRequirements)"
41305
+ }
41306
+ ]
41307
+ }
41308
+ },
41309
+ {
41310
+ name: "multipleEffectProvide",
41311
+ group: "antipattern",
41312
+ description: "Warns against chaining Effect.provide calls which can cause service lifecycle issues",
41313
+ defaultSeverity: "warning",
41314
+ fixable: true,
41315
+ supportedEffect: [
41316
+ "v3",
41317
+ "v4"
41318
+ ],
41319
+ preview: {
41320
+ 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',
41321
+ diagnostics: [
41322
+ {
41323
+ start: 348,
41324
+ end: 373,
41325
+ 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)"
41326
+ }
41327
+ ]
41328
+ }
41329
+ },
41330
+ {
41331
+ name: "returnEffectInGen",
41332
+ group: "antipattern",
41333
+ description: "Warns when returning an Effect in a generator causes nested Effect<Effect<...>>",
41334
+ defaultSeverity: "suggestion",
41335
+ fixable: true,
41336
+ supportedEffect: [
41337
+ "v3",
41338
+ "v4"
41339
+ ],
41340
+ preview: {
41341
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n return Effect.succeed(1)\n})\n',
41342
+ diagnostics: [
41343
+ {
41344
+ start: 91,
41345
+ end: 115,
41346
+ 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)"
41347
+ }
41348
+ ]
41349
+ }
41350
+ },
41351
+ {
41352
+ name: "runEffectInsideEffect",
41353
+ group: "antipattern",
41354
+ description: "Suggests using Runtime methods instead of Effect.run* inside Effect contexts",
41355
+ defaultSeverity: "suggestion",
41356
+ fixable: true,
41357
+ supportedEffect: [
41358
+ "v3"
41359
+ ],
41360
+ preview: {
41361
+ 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',
41362
+ diagnostics: [
41363
+ {
41364
+ start: 101,
41365
+ end: 115,
41366
+ 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)"
41367
+ }
41368
+ ]
41369
+ }
41370
+ },
41371
+ {
41372
+ name: "schemaSyncInEffect",
41373
+ group: "antipattern",
41374
+ description: "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
41375
+ defaultSeverity: "suggestion",
41376
+ fixable: false,
41377
+ supportedEffect: [
41378
+ "v3"
41379
+ ],
41380
+ preview: {
41381
+ 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',
41382
+ diagnostics: [
41383
+ {
41384
+ start: 276,
41385
+ end: 293,
41386
+ text: "Using Schema.decodeSync inside an Effect generator is not recommended. Use Schema.decode instead to get properly typed error channel. effect(schemaSyncInEffect)"
41387
+ }
41388
+ ]
41389
+ }
41390
+ },
41391
+ {
41392
+ name: "scopeInLayerEffect",
41393
+ group: "antipattern",
41394
+ description: "Suggests using Layer.scoped instead of Layer.effect when Scope is in requirements",
41395
+ defaultSeverity: "warning",
41396
+ fixable: true,
41397
+ supportedEffect: [
41398
+ "v3"
41399
+ ],
41400
+ preview: {
41401
+ 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',
41402
+ diagnostics: [
41403
+ {
41404
+ start: 211,
41405
+ end: 338,
41406
+ 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)'
41407
+ }
41408
+ ]
41409
+ }
41410
+ },
41411
+ {
41412
+ name: "strictEffectProvide",
41413
+ group: "antipattern",
41414
+ description: "Warns when using Effect.provide with layers outside of application entry points",
41415
+ defaultSeverity: "off",
41416
+ fixable: false,
41417
+ supportedEffect: [
41418
+ "v3",
41419
+ "v4"
41420
+ ],
41421
+ preview: {
41422
+ 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',
41423
+ diagnostics: [
41424
+ {
41425
+ start: 235,
41426
+ end: 265,
41427
+ 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)"
41428
+ }
41429
+ ]
41430
+ }
41431
+ },
41432
+ {
41433
+ name: "tryCatchInEffectGen",
41434
+ group: "antipattern",
41435
+ description: "Discourages try/catch in Effect generators in favor of Effect error handling",
41436
+ defaultSeverity: "suggestion",
41437
+ fixable: false,
41438
+ supportedEffect: [
41439
+ "v3",
41440
+ "v4"
41441
+ ],
41442
+ preview: {
41443
+ 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',
41444
+ diagnostics: [
41445
+ {
41446
+ start: 91,
41447
+ end: 151,
41448
+ 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)"
41449
+ }
41450
+ ]
41451
+ }
41452
+ },
41453
+ {
41454
+ name: "unknownInEffectCatch",
41455
+ group: "antipattern",
41456
+ description: "Warns when catch callbacks return unknown instead of typed errors",
41457
+ defaultSeverity: "warning",
41458
+ fixable: false,
41459
+ supportedEffect: [
41460
+ "v3",
41461
+ "v4"
41462
+ ],
41463
+ preview: {
41464
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.tryPromise({\n try: async () => 1,\n catch: (error) => error\n})\n',
41465
+ diagnostics: [
41466
+ {
41467
+ start: 56,
41468
+ end: 73,
41469
+ 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)"
41470
+ }
41471
+ ]
41472
+ }
41473
+ },
41474
+ {
41475
+ name: "extendsNativeError",
41476
+ group: "effectNative",
41477
+ description: "Warns when a class directly extends the native Error class",
41478
+ defaultSeverity: "off",
41479
+ fixable: false,
41480
+ supportedEffect: [
41481
+ "v3",
41482
+ "v4"
41483
+ ],
41484
+ preview: {
41485
+ sourceText: "\nexport class PreviewError extends Error {}\n",
41486
+ diagnostics: [
41487
+ {
41488
+ start: 14,
41489
+ end: 26,
41490
+ 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)"
41491
+ }
41492
+ ]
41493
+ }
41494
+ },
41495
+ {
41496
+ name: "globalFetch",
41497
+ group: "effectNative",
41498
+ description: "Warns when using the global fetch function instead of the Effect HTTP client",
41499
+ defaultSeverity: "off",
41500
+ fixable: false,
41501
+ supportedEffect: [
41502
+ "v3",
41503
+ "v4"
41504
+ ],
41505
+ preview: {
41506
+ sourceText: '\nexport const preview = fetch("https://example.com")\n',
41507
+ diagnostics: [
41508
+ {
41509
+ start: 24,
41510
+ end: 29,
41511
+ text: "Prefer using HttpClient from @effect/platform instead of the global 'fetch' function. effect(globalFetch)"
41512
+ }
41513
+ ]
41514
+ }
41515
+ },
41516
+ {
41517
+ name: "instanceOfSchema",
41518
+ group: "effectNative",
41519
+ description: "Suggests using Schema.is instead of instanceof for Effect Schema types",
41520
+ defaultSeverity: "off",
41521
+ fixable: true,
41522
+ supportedEffect: [
41523
+ "v3",
41524
+ "v4"
41525
+ ],
41526
+ preview: {
41527
+ 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',
41528
+ diagnostics: [
41529
+ {
41530
+ start: 159,
41531
+ end: 180,
41532
+ text: "Consider using Schema.is instead of instanceof for Effect Schema types. effect(instanceOfSchema)"
41533
+ }
41534
+ ]
41535
+ }
41536
+ },
41537
+ {
41538
+ name: "nodeBuiltinImport",
41539
+ group: "effectNative",
41540
+ description: "Warns when importing Node.js built-in modules that have Effect-native counterparts",
41541
+ defaultSeverity: "off",
41542
+ fixable: false,
41543
+ supportedEffect: [
41544
+ "v3",
41545
+ "v4"
41546
+ ],
41547
+ preview: {
41548
+ sourceText: 'import fs from "node:fs"\n\nexport const preview = fs.readFileSync\n',
41549
+ diagnostics: [
41550
+ {
41551
+ start: 15,
41552
+ end: 24,
41553
+ text: "Prefer using FileSystem from @effect/platform instead of the Node.js 'fs' module. effect(nodeBuiltinImport)"
41554
+ }
41555
+ ]
41556
+ }
41557
+ },
41558
+ {
41559
+ name: "preferSchemaOverJson",
41560
+ group: "effectNative",
41561
+ description: "Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify which may throw",
41562
+ defaultSeverity: "suggestion",
41563
+ fixable: false,
41564
+ supportedEffect: [
41565
+ "v3",
41566
+ "v4"
41567
+ ],
41568
+ preview: {
41569
+ sourceText: `import { Effect } from "effect"
41570
+
41571
+ export const preview = Effect.gen(function*() {
41572
+ const text = yield* Effect.succeed('{"ok":true}')
41573
+ return JSON.parse(text)
41574
+ })
41575
+ `,
41576
+ diagnostics: [
41577
+ {
41578
+ start: 142,
41579
+ end: 158,
41580
+ text: "Consider using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify effect(preferSchemaOverJson)"
41581
+ }
41582
+ ]
41583
+ }
41584
+ },
41585
+ {
41586
+ name: "catchAllToMapError",
41587
+ group: "style",
41588
+ description: "Suggests using Effect.mapError instead of Effect.catchAll when the callback only wraps the error with Effect.fail",
41589
+ defaultSeverity: "suggestion",
41590
+ fixable: true,
41591
+ supportedEffect: [
41592
+ "v3",
41593
+ "v4"
41594
+ ],
41595
+ preview: {
41596
+ 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',
41597
+ diagnostics: [
41598
+ {
41599
+ start: 150,
41600
+ end: 162,
41601
+ text: "You can use Effect.mapError instead of Effect.catch + Effect.fail to transform the error type. effect(catchAllToMapError)"
41602
+ }
41603
+ ]
41604
+ }
41605
+ },
41606
+ {
41607
+ name: "deterministicKeys",
41608
+ group: "style",
41609
+ description: "Enforces deterministic naming for service/tag/error identifiers based on class names",
41610
+ defaultSeverity: "off",
41611
+ fixable: true,
41612
+ supportedEffect: [
41613
+ "v3",
41614
+ "v4"
41615
+ ],
41616
+ preview: {
41617
+ sourceText: 'import { ServiceMap } from "effect"\n\nexport class RenamedService\n extends ServiceMap.Service<RenamedService, {}>()("CustomIdentifier") {}\n',
41618
+ diagnostics: [
41619
+ {
41620
+ start: 116,
41621
+ end: 134,
41622
+ text: "Key should be '@effect/harness-effect-v4/examples/diagnostics/deterministicKeys_preview/RenamedService' effect(deterministicKeys)"
41623
+ }
41624
+ ]
41625
+ }
41626
+ },
41627
+ {
41628
+ name: "effectFnOpportunity",
41629
+ group: "style",
41630
+ description: "Suggests using Effect.fn for functions that returns an Effect",
41631
+ defaultSeverity: "suggestion",
41632
+ fixable: true,
41633
+ supportedEffect: [
41634
+ "v3",
41635
+ "v4"
41636
+ ],
41637
+ preview: {
41638
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = () => Effect.gen(function*() {\n return yield* Effect.succeed(1)\n})\n',
41639
+ diagnostics: [
41640
+ {
41641
+ start: 54,
41642
+ end: 61,
41643
+ text: "Can be rewritten as a reusable function: Effect.fn(function*() { ... }) effect(effectFnOpportunity)"
41644
+ }
41645
+ ]
41646
+ }
41647
+ },
41648
+ {
41649
+ name: "effectMapVoid",
41650
+ group: "style",
41651
+ description: "Suggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {})",
41652
+ defaultSeverity: "suggestion",
41653
+ fixable: true,
41654
+ supportedEffect: [
41655
+ "v3",
41656
+ "v4"
41657
+ ],
41658
+ preview: {
41659
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.succeed(1).pipe(\n Effect.map(() => undefined)\n)\n',
41660
+ diagnostics: [
41661
+ {
41662
+ start: 82,
41663
+ end: 92,
41664
+ text: "Effect.asVoid can be used instead to discard the success value effect(effectMapVoid)"
41665
+ }
41666
+ ]
41667
+ }
41668
+ },
41669
+ {
41670
+ name: "effectSucceedWithVoid",
41671
+ group: "style",
41672
+ description: "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
41673
+ defaultSeverity: "suggestion",
41674
+ fixable: true,
41675
+ supportedEffect: [
41676
+ "v3",
41677
+ "v4"
41678
+ ],
41679
+ preview: {
41680
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.succeed(undefined)\n',
41681
+ diagnostics: [
41682
+ {
41683
+ start: 56,
41684
+ end: 81,
41685
+ text: "Effect.void can be used instead of Effect.succeed(undefined) or Effect.succeed(void 0) effect(effectSucceedWithVoid)"
41686
+ }
41687
+ ]
41688
+ }
41689
+ },
41690
+ {
41691
+ name: "importFromBarrel",
41692
+ group: "style",
41693
+ description: "Suggests importing from specific module paths instead of barrel exports",
41694
+ defaultSeverity: "off",
41695
+ fixable: true,
41696
+ supportedEffect: [
41697
+ "v3",
41698
+ "v4"
41699
+ ],
41700
+ preview: {
41701
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.void\n',
41702
+ diagnostics: [
41703
+ {
41704
+ start: 9,
41705
+ end: 15,
41706
+ text: "Importing from barrel module effect is not allowed. effect(importFromBarrel)"
41707
+ }
41708
+ ]
41709
+ }
41710
+ },
41711
+ {
41712
+ name: "missedPipeableOpportunity",
41713
+ group: "style",
41714
+ description: "Enforces the use of pipeable style for nested function calls",
41715
+ defaultSeverity: "off",
41716
+ fixable: true,
41717
+ supportedEffect: [
41718
+ "v3",
41719
+ "v4"
41720
+ ],
41721
+ preview: {
41722
+ 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',
41723
+ diagnostics: [
41724
+ {
41725
+ start: 116,
41726
+ end: 172,
41727
+ text: "Nested function calls can be converted to pipeable style for better readability; consider using Schema.decodeEffect(User)({ id: 1 }).pipe(...) instead. effect(missedPipeableOpportunity)"
41728
+ }
41729
+ ]
41730
+ }
41731
+ },
41732
+ {
41733
+ name: "missingEffectServiceDependency",
41734
+ group: "style",
41735
+ description: "Checks that Effect.Service dependencies satisfy all required layer inputs",
41736
+ defaultSeverity: "off",
41737
+ fixable: false,
41738
+ supportedEffect: [
41739
+ "v3"
41740
+ ],
41741
+ preview: {
41742
+ 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',
41743
+ diagnostics: [
41744
+ {
41745
+ start: 128,
41746
+ end: 132,
41747
+ text: "Service 'Db' is required but not provided by dependencies effect(missingEffectServiceDependency)"
41748
+ }
41749
+ ]
41750
+ }
41751
+ },
41752
+ {
41753
+ name: "redundantSchemaTagIdentifier",
41754
+ group: "style",
41755
+ description: "Suggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest",
41756
+ defaultSeverity: "suggestion",
41757
+ fixable: true,
41758
+ supportedEffect: [
41759
+ "v3",
41760
+ "v4"
41761
+ ],
41762
+ preview: {
41763
+ sourceText: 'import * as Schema from "effect/Schema"\n\nexport class Preview\n extends Schema.TaggedClass<Preview>("Preview")("Preview", {\n value: Schema.String\n }) {}\n',
41764
+ diagnostics: [
41765
+ {
41766
+ start: 100,
41767
+ end: 109,
41768
+ text: "Identifier 'Preview' is redundant since it equals the _tag value effect(redundantSchemaTagIdentifier)"
41769
+ }
41770
+ ]
41771
+ }
41772
+ },
41773
+ {
41774
+ name: "schemaStructWithTag",
41775
+ group: "style",
41776
+ description: "Suggests using Schema.TaggedStruct instead of Schema.Struct with _tag field",
41777
+ defaultSeverity: "suggestion",
41778
+ fixable: true,
41779
+ supportedEffect: [
41780
+ "v3",
41781
+ "v4"
41782
+ ],
41783
+ preview: {
41784
+ sourceText: 'import * as Schema from "effect/Schema"\n\nexport const preview = Schema.Struct({\n _tag: Schema.Literal("User"),\n name: Schema.String\n})\n',
41785
+ diagnostics: [
41786
+ {
41787
+ start: 64,
41788
+ end: 136,
41789
+ text: "Schema.Struct with a _tag field can be simplified to Schema.TaggedStruct to make the tag optional in the constructor. effect(schemaStructWithTag)"
41790
+ }
41791
+ ]
41792
+ }
41793
+ },
41794
+ {
41795
+ name: "schemaUnionOfLiterals",
41796
+ group: "style",
41797
+ description: "Simplifies Schema.Union of multiple Schema.Literal calls into single Schema.Literal",
41798
+ defaultSeverity: "off",
41799
+ fixable: true,
41800
+ supportedEffect: [
41801
+ "v3"
41802
+ ],
41803
+ preview: {
41804
+ sourceText: 'import * as Schema from "effect/Schema"\n\nexport const preview = Schema.Union(Schema.Literal("a"), Schema.Literal("b"))\n',
41805
+ diagnostics: [
41806
+ {
41807
+ start: 64,
41808
+ end: 118,
41809
+ text: "A Schema.Union of multiple Schema.Literal calls can be simplified to a single Schema.Literal call. effect(schemaUnionOfLiterals)"
41810
+ }
41811
+ ]
41812
+ }
41813
+ },
41814
+ {
41815
+ name: "serviceNotAsClass",
41816
+ group: "style",
41817
+ description: "Warns when ServiceMap.Service is used as a variable instead of a class declaration",
41818
+ defaultSeverity: "off",
41819
+ fixable: true,
41820
+ supportedEffect: [
41821
+ "v4"
41822
+ ],
41823
+ preview: {
41824
+ sourceText: 'import { ServiceMap } from "effect"\n\nexport const Preview = ServiceMap.Service<{ port: number }>("Preview")\n',
41825
+ diagnostics: [
41826
+ {
41827
+ start: 60,
41828
+ end: 107,
41829
+ 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)'
41830
+ }
41831
+ ]
41832
+ }
41833
+ },
41834
+ {
41835
+ name: "strictBooleanExpressions",
41836
+ group: "style",
41837
+ description: "Enforces boolean types in conditional expressions for type safety",
41838
+ defaultSeverity: "off",
41839
+ fixable: false,
41840
+ supportedEffect: [
41841
+ "v3",
41842
+ "v4"
41843
+ ],
41844
+ preview: {
41845
+ sourceText: "\ndeclare const value: string | undefined\nexport const preview = value ? 1 : 0\n",
41846
+ diagnostics: [
41847
+ {
41848
+ start: 64,
41849
+ end: 69,
41850
+ text: "Unexpected `string` type in condition, expected strictly a boolean instead. effect(strictBooleanExpressions)"
41851
+ },
41852
+ {
41853
+ start: 64,
41854
+ end: 69,
41855
+ text: "Unexpected `undefined` type in condition, expected strictly a boolean instead. effect(strictBooleanExpressions)"
41856
+ }
41857
+ ]
41858
+ }
41859
+ },
41860
+ {
41861
+ name: "unnecessaryEffectGen",
41862
+ group: "style",
41863
+ description: "Suggests removing Effect.gen when it contains only a single return statement",
41864
+ defaultSeverity: "suggestion",
41865
+ fixable: true,
41866
+ supportedEffect: [
41867
+ "v3",
41868
+ "v4"
41869
+ ],
41870
+ preview: {
41871
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n return yield* Effect.succeed(1)\n})\n',
41872
+ diagnostics: [
41873
+ {
41874
+ start: 64,
41875
+ end: 125,
41876
+ text: "This Effect.gen contains a single return statement. effect(unnecessaryEffectGen)"
41877
+ }
41878
+ ]
41879
+ }
41880
+ },
41881
+ {
41882
+ name: "unnecessaryFailYieldableError",
41883
+ group: "style",
41884
+ description: "Suggests yielding yieldable errors directly instead of wrapping with Effect.fail",
41885
+ defaultSeverity: "suggestion",
41886
+ fixable: true,
41887
+ supportedEffect: [
41888
+ "v3",
41889
+ "v4"
41890
+ ],
41891
+ preview: {
41892
+ 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',
41893
+ diagnostics: [
41894
+ {
41895
+ start: 178,
41896
+ end: 208,
41897
+ text: "This Effect.fail call uses a yieldable error type as argument. You can yield* the error directly instead. effect(unnecessaryFailYieldableError)"
41898
+ }
41899
+ ]
41900
+ }
41901
+ },
41902
+ {
41903
+ name: "unnecessaryPipe",
41904
+ group: "style",
41905
+ description: "Removes pipe calls with no arguments",
41906
+ defaultSeverity: "suggestion",
41907
+ fixable: true,
41908
+ supportedEffect: [
41909
+ "v3",
41910
+ "v4"
41911
+ ],
41912
+ preview: {
41913
+ sourceText: 'import { pipe } from "effect/Function"\n\nexport const preview = pipe(1)\n',
41914
+ diagnostics: [
41915
+ {
41916
+ start: 63,
41917
+ end: 70,
41918
+ text: "This pipe call contains no arguments. effect(unnecessaryPipe)"
41919
+ }
41920
+ ]
41921
+ }
41922
+ },
41923
+ {
41924
+ name: "unnecessaryPipeChain",
41925
+ group: "style",
41926
+ description: "Simplifies chained pipe calls into a single pipe call",
41927
+ defaultSeverity: "suggestion",
41928
+ fixable: true,
41929
+ supportedEffect: [
41930
+ "v3",
41931
+ "v4"
41932
+ ],
41933
+ preview: {
41934
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.succeed(1).pipe(Effect.asVoid).pipe(Effect.as("done"))\n',
41935
+ diagnostics: [
41936
+ {
41937
+ start: 64,
41938
+ end: 125,
41939
+ text: "Chained pipe calls can be simplified to a single pipe call effect(unnecessaryPipeChain)"
41940
+ }
41941
+ ]
41942
+ }
41943
+ }
41944
+ ]
41945
+ };
41946
+
41947
+ // src/cli/setup/diagnostic-info.ts
41948
+ var diagnosticMetadata = metadata_default;
41949
+ function getDiagnosticGroups() {
41950
+ return diagnosticMetadata.groups;
40674
41951
  }
40675
- function handleClear(options) {
41952
+ function getDiagnosticMetadataRules() {
41953
+ return diagnosticMetadata.rules;
41954
+ }
41955
+ function getAllDiagnostics() {
41956
+ const diagnosticsByName = new Map(diagnostics.map((diagnostic) => [diagnostic.name, diagnostic]));
41957
+ return getDiagnosticMetadataRules().flatMap((metadataRule) => {
41958
+ const diagnostic = diagnosticsByName.get(metadataRule.name);
41959
+ if (!diagnostic) {
41960
+ return [];
41961
+ }
41962
+ return [{
41963
+ name: diagnostic.name,
41964
+ code: diagnostic.code,
41965
+ group: metadataRule.group,
41966
+ defaultSeverity: diagnostic.severity,
41967
+ description: metadataRule.description,
41968
+ preview: metadataRule.preview
41969
+ }];
41970
+ });
41971
+ }
41972
+ function cycleSeverity(current, direction) {
41973
+ const order = ["off", "suggestion", "message", "warning", "error"];
41974
+ const currentIndex = order.indexOf(current);
41975
+ if (direction === "right") {
41976
+ return order[(currentIndex + 1) % order.length];
41977
+ } else {
41978
+ return order[(currentIndex - 1 + order.length) % order.length];
41979
+ }
41980
+ }
41981
+ var shortNames = {
41982
+ off: "off",
41983
+ suggestion: "sugg",
41984
+ message: "info",
41985
+ warning: "warn",
41986
+ error: "err"
41987
+ };
41988
+ var MAX_SEVERITY_LENGTH = Object.values(shortNames).reduce((max2, name) => Math.max(max2, name.length), 0);
41989
+ function getSeverityShortName(severity) {
41990
+ return shortNames[severity] ?? "???";
41991
+ }
41992
+
41993
+ // src/cli/setup/diagnostic-prompt.ts
41994
+ var diagnosticGroups = getDiagnosticGroups();
41995
+ var Action2 = taggedEnum();
41996
+ var SEARCH_ICON = "/";
41997
+ var MIN_PREVIEW_AND_MESSAGES_LINES = 18;
41998
+ function getControlsLegend(searchText) {
41999
+ const searchLegend = searchText.length === 0 ? `${SEARCH_ICON} type to search` : `${SEARCH_ICON} searching: ${searchText}`;
42000
+ return `\u2190/\u2192 change rule \u2191/\u2193 change severity ${searchLegend}`;
42001
+ }
42002
+ function getSeveritySymbol(severity) {
42003
+ const symbols = {
42004
+ off: ".",
42005
+ suggestion: "?",
42006
+ message: "i",
42007
+ warning: "!",
42008
+ error: "x"
42009
+ };
42010
+ return symbols[severity];
42011
+ }
42012
+ function getSeverityStyle(severity) {
42013
+ const styles = {
42014
+ off: DIM,
42015
+ suggestion: DIM,
42016
+ message: BLUE,
42017
+ warning: YELLOW,
42018
+ error: RED
42019
+ };
42020
+ return styles[severity];
42021
+ }
42022
+ function renderEntry(entry, severity, isSelected) {
42023
+ const symbol4 = ansi(getSeveritySymbol(severity), getSeverityStyle(severity));
42024
+ const name = isSelected ? ansi(entry.name, UNDERLINE) : entry.name;
42025
+ return `${symbol4} ${name}`;
42026
+ }
42027
+ function rowsForLength(length, columns) {
42028
+ if (columns <= 0) {
42029
+ return 1;
42030
+ }
42031
+ return Math.max(1, 1 + Math.floor(Math.max(length - 1, 0) / columns));
42032
+ }
42033
+ function eraseRenderedLines(lines2, columns) {
42034
+ let result3 = "";
42035
+ for (let lineIndex = lines2.length - 1; lineIndex >= 0; lineIndex--) {
42036
+ const rows = rowsForLength(visibleLength(lines2[lineIndex] ?? ""), columns);
42037
+ for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
42038
+ result3 += ERASE_LINE;
42039
+ if (!(lineIndex === 0 && rowIndex === rows - 1)) {
42040
+ result3 += "\x1B[1A";
42041
+ }
42042
+ }
42043
+ }
42044
+ if (lines2.length > 0) {
42045
+ result3 += CURSOR_LEFT;
42046
+ }
42047
+ return result3;
42048
+ }
42049
+ function wrapPaddedText(text2, initialPadding, endPadding, columns) {
42050
+ if (text2.length === 0) {
42051
+ return [initialPadding + endPadding];
42052
+ }
42053
+ const available = Math.max(columns - visibleLength(initialPadding) - visibleLength(endPadding), 1);
42054
+ const lines2 = [];
42055
+ let remaining = text2;
42056
+ while (remaining.length > 0) {
42057
+ const chunk = remaining.slice(0, available);
42058
+ lines2.push(initialPadding + chunk + endPadding);
42059
+ remaining = remaining.slice(chunk.length);
42060
+ }
42061
+ return lines2;
42062
+ }
42063
+ function wrapListItemText(text2, firstLinePadding, continuationPadding, endPadding, columns) {
42064
+ if (text2.length === 0) {
42065
+ return [firstLinePadding + endPadding];
42066
+ }
42067
+ const firstAvailable = Math.max(columns - visibleLength(firstLinePadding) - visibleLength(endPadding), 1);
42068
+ const continuationAvailable = Math.max(
42069
+ columns - visibleLength(continuationPadding) - visibleLength(endPadding),
42070
+ 1
42071
+ );
42072
+ const lines2 = [];
42073
+ let remaining = text2;
42074
+ let isFirstLine = true;
42075
+ while (remaining.length > 0) {
42076
+ const padding = isFirstLine ? firstLinePadding : continuationPadding;
42077
+ const available = isFirstLine ? firstAvailable : continuationAvailable;
42078
+ const chunk = remaining.slice(0, available);
42079
+ lines2.push(padding + chunk + endPadding);
42080
+ remaining = remaining.slice(chunk.length);
42081
+ isFirstLine = false;
42082
+ }
42083
+ return lines2;
42084
+ }
42085
+ function renderPaddedLine(text2, initialPadding, endPadding, columns) {
42086
+ const available = Math.max(columns - visibleLength(initialPadding) - visibleLength(endPadding), 0);
42087
+ const truncated = visibleLength(text2) <= available ? text2 : text2.slice(0, available);
42088
+ const padding = Math.max(available - visibleLength(truncated), 0);
42089
+ return initialPadding + truncated + " ".repeat(padding) + endPadding;
42090
+ }
42091
+ function mergeHighlightRanges(ranges) {
42092
+ const sorted = ranges.filter((range2) => range2.end > range2.start).slice().sort((a, b) => a.start - b.start);
42093
+ const merged = [];
42094
+ for (const range2 of sorted) {
42095
+ const previous = merged[merged.length - 1];
42096
+ if (!previous || range2.start > previous.end) {
42097
+ merged.push(range2);
42098
+ continue;
42099
+ }
42100
+ merged[merged.length - 1] = {
42101
+ start: previous.start,
42102
+ end: Math.max(previous.end, range2.end)
42103
+ };
42104
+ }
42105
+ return merged;
42106
+ }
42107
+ function stylePreviewLine(line, lineStart, ranges) {
42108
+ if (line.length === 0) {
42109
+ return "";
42110
+ }
42111
+ const lineEnd = lineStart + line.length;
42112
+ let cursor = 0;
42113
+ let rendered = "";
42114
+ for (const range2 of ranges) {
42115
+ const start = Math.max(range2.start, lineStart);
42116
+ const end = Math.min(range2.end, lineEnd);
42117
+ if (end <= start) {
42118
+ continue;
42119
+ }
42120
+ const startIndex = start - lineStart;
42121
+ const endIndex = end - lineStart;
42122
+ if (cursor < startIndex) {
42123
+ rendered += ansi(line.slice(cursor, startIndex), DIM);
42124
+ }
42125
+ rendered += ansi(line.slice(startIndex, endIndex), UNDERLINE);
42126
+ cursor = endIndex;
42127
+ }
42128
+ if (cursor < line.length) {
42129
+ rendered += ansi(line.slice(cursor), DIM);
42130
+ }
42131
+ return rendered;
42132
+ }
42133
+ function wrapSourceLine(line, lineStart, availableWidth) {
42134
+ if (line.length === 0) {
42135
+ return [{ text: "", start: lineStart, end: lineStart }];
42136
+ }
42137
+ const width = Math.max(availableWidth, 1);
42138
+ const wrapped = [];
42139
+ let offset = 0;
42140
+ while (offset < line.length) {
42141
+ const text2 = line.slice(offset, offset + width);
42142
+ wrapped.push({
42143
+ text: text2,
42144
+ start: lineStart + offset,
42145
+ end: lineStart + offset + text2.length
42146
+ });
42147
+ offset += text2.length;
42148
+ }
42149
+ return wrapped;
42150
+ }
42151
+ function wrapPreviewSourceText(sourceText, columns) {
42152
+ const availableWidth = Math.max(columns - 2, 1);
42153
+ const logicalLines = sourceText.split("\n");
42154
+ const wrapped = [];
42155
+ let offset = 0;
42156
+ for (const line of logicalLines) {
42157
+ for (const wrappedLine of wrapSourceLine(line, offset, availableWidth)) {
42158
+ wrapped.push(wrappedLine);
42159
+ }
42160
+ offset += line.length + 1;
42161
+ }
42162
+ return wrapped;
42163
+ }
42164
+ function renderPreviewSourceText(sourceText, diagnostics3, columns) {
42165
+ const ranges = mergeHighlightRanges(diagnostics3.map((diagnostic) => ({
42166
+ start: diagnostic.start,
42167
+ end: diagnostic.end
42168
+ })));
42169
+ return wrapPreviewSourceText(sourceText, columns).map((line) => {
42170
+ const rendered = stylePreviewLine(line.text, line.start, ranges);
42171
+ return CURSOR_TO_0 + renderPaddedLine(rendered, " ", "", columns);
42172
+ });
42173
+ }
42174
+ function renderPreviewMessages(diagnostics3, columns) {
42175
+ const messages = Array.from(new Set(diagnostics3.map((diagnostic) => diagnostic.text)));
42176
+ return messages.flatMap((message) => {
42177
+ let isFirstBlock = true;
42178
+ return message.split("\n").flatMap((line) => {
42179
+ const wrappedLines = wrapListItemText(line, isFirstBlock ? "- " : " ", " ", "", columns);
42180
+ isFirstBlock = false;
42181
+ return wrappedLines.map((wrappedLine) => CURSOR_TO_0 + ansi(wrappedLine, DIM + ITALIC));
42182
+ });
42183
+ });
42184
+ }
42185
+ function renderDivider(columns, legendText) {
42186
+ if (legendText === void 0) {
42187
+ return ansi("\u2500".repeat(Math.max(columns, 0)), DIM);
42188
+ }
42189
+ const legend = ` ${legendText} `;
42190
+ const legendLength = visibleLength(legend);
42191
+ if (columns <= legendLength) {
42192
+ return ansi(legend.slice(0, Math.max(columns, 0)), DIM);
42193
+ }
42194
+ const remaining = columns - legendLength;
42195
+ const left = "\u2500".repeat(Math.floor(remaining / 2));
42196
+ const right = "\u2500".repeat(remaining - left.length);
42197
+ return ansi(left + legend + right, DIM);
42198
+ }
42199
+ function renderSelectedRuleDivider(selected, severities, columns) {
42200
+ if (!selected) {
42201
+ return renderDivider(columns);
42202
+ }
42203
+ const currentSeverity = severities[selected.name] ?? selected.defaultSeverity;
42204
+ const text2 = `${selected.name} (currently set as ${getSeverityShortName(currentSeverity)})`;
42205
+ return renderDivider(columns, text2);
42206
+ }
42207
+ function matchesSearch(entry, searchText) {
42208
+ if (searchText.length === 0) {
42209
+ return true;
42210
+ }
42211
+ const normalized = searchText.toLowerCase();
42212
+ return entry.name.toLowerCase().includes(normalized) || entry.description.toLowerCase().includes(normalized) || entry.previewSourceText.toLowerCase().includes(normalized);
42213
+ }
42214
+ function getFilteredEntries(entries2, searchText) {
42215
+ return entries2.filter((entry) => matchesSearch(entry, searchText));
42216
+ }
42217
+ function normalizeStartIndex(length, startIndex) {
42218
+ if (length <= 0) {
42219
+ return 0;
42220
+ }
42221
+ return (startIndex % length + length) % length;
42222
+ }
42223
+ function isPrintableInput(input) {
42224
+ const printablePattern = new RegExp(String.raw`^[^\u0000-\u001F\u007F]+$`, "u");
42225
+ return !input.key.ctrl && !input.key.meta && input.input !== void 0 && input.input.length > 0 && printablePattern.test(input.input);
42226
+ }
42227
+ function buildVisibleEntries(entries2, severities, startIndex, columns) {
42228
+ if (entries2.length === 0) {
42229
+ return {
42230
+ fullLine: renderPaddedLine(ansi("No matching rules", DIM), " ", " ", columns),
42231
+ visibleRules: []
42232
+ };
42233
+ }
42234
+ const reservedColumns = 4;
42235
+ const itemColumns = Math.max(columns - reservedColumns, 0);
42236
+ const separator = " ";
42237
+ const visibleEntries = [];
42238
+ const visibleRules = [];
42239
+ let currentLength = 0;
42240
+ let seenCount = 0;
42241
+ while (seenCount < entries2.length) {
42242
+ const index = (startIndex + seenCount) % entries2.length;
42243
+ const rule = entries2[index];
42244
+ const currentSeverity = severities[rule.name] ?? rule.defaultSeverity;
42245
+ const entry = renderEntry(rule, currentSeverity, seenCount === 0);
42246
+ const nextLength = visibleEntries.length === 0 ? visibleLength(entry) : currentLength + separator.length + visibleLength(entry);
42247
+ if (nextLength > itemColumns) {
42248
+ break;
42249
+ }
42250
+ visibleEntries.push(entry);
42251
+ visibleRules.push(rule);
42252
+ currentLength = nextLength;
42253
+ seenCount++;
42254
+ }
42255
+ const leftMarker = entries2.length > 1 ? ansi("\u2190", DIM) : " ";
42256
+ const rightMarker = entries2.length > 1 ? ansi("\u2192", DIM) : " ";
42257
+ return {
42258
+ fullLine: renderPaddedLine(visibleEntries.join(separator), `${leftMarker} `, ` ${rightMarker}`, columns),
42259
+ visibleRules
42260
+ };
42261
+ }
42262
+ function buildGroupLine(selected, columns) {
42263
+ if (!selected) {
42264
+ return renderPaddedLine("", " ", " ", columns);
42265
+ }
42266
+ const selectedIndex = diagnosticGroups.findIndex((group) => group.id === selected.group);
42267
+ const rotatedGroups = diagnosticGroups.map(
42268
+ (_, index) => diagnosticGroups[(selectedIndex + index) % diagnosticGroups.length]
42269
+ );
42270
+ const content = rotatedGroups.map((group, index) => {
42271
+ const label = group.name.toUpperCase();
42272
+ return index === 0 ? ansi(label, BOLD) : ansi(label, DIM);
42273
+ }).join(" ");
42274
+ return renderPaddedLine(content, " ", " ", columns);
42275
+ }
42276
+ function buildFrame(entries2, searchText, severities, startIndex, columns) {
42277
+ const visible = buildVisibleEntries(entries2, severities, startIndex, columns);
42278
+ const selected = visible.visibleRules[0];
42279
+ const wrappedDescription = wrapPaddedText(selected?.description ?? "", " ", "", columns);
42280
+ const previewLines = renderPreviewSourceText(
42281
+ selected?.previewSourceText ?? "",
42282
+ selected?.previewDiagnostics ?? [],
42283
+ columns
42284
+ );
42285
+ const previewMessages = renderPreviewMessages(selected?.previewDiagnostics ?? [], columns);
42286
+ const previewSectionLines = [...previewLines, ...previewMessages];
42287
+ const paddingLines = Array.from(
42288
+ { length: Math.max(MIN_PREVIEW_AND_MESSAGES_LINES - previewSectionLines.length, 0) },
42289
+ () => ""
42290
+ );
42291
+ return [
42292
+ CURSOR_HIDE + renderSelectedRuleDivider(selected, severities, columns),
42293
+ ...previewSectionLines,
42294
+ ...paddingLines,
42295
+ CURSOR_TO_0 + renderDivider(columns),
42296
+ CURSOR_TO_0 + buildGroupLine(selected, columns),
42297
+ CURSOR_TO_0 + visible.fullLine,
42298
+ ...wrappedDescription.map((line) => CURSOR_TO_0 + ansi(line, DIM)),
42299
+ CURSOR_TO_0 + renderDivider(columns, getControlsLegend(searchText)),
42300
+ ""
42301
+ ];
42302
+ }
42303
+ function buildState(entries2, startIndex, searchText, severities) {
40676
42304
  return gen2(function* () {
40677
42305
  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;
42306
+ const columns = Math.max(yield* terminal.columns, 1);
42307
+ const filteredEntries = getFilteredEntries(entries2, searchText);
42308
+ const normalizedStartIndex = normalizeStartIndex(filteredEntries.length, startIndex);
42309
+ const renderedLines = buildFrame(filteredEntries, searchText, severities, normalizedStartIndex, columns);
42310
+ return {
42311
+ startIndex: normalizedStartIndex,
42312
+ searchText,
42313
+ severities,
42314
+ renderedColumns: columns,
42315
+ renderedLines
42316
+ };
40684
42317
  });
40685
42318
  }
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
- });
42319
+ function renderSubmission(state, entries2) {
42320
+ return succeed6(
42321
+ CURSOR_TO_0 + JSON.stringify(
42322
+ Object.fromEntries(
42323
+ entries2.flatMap((entry) => {
42324
+ const severity = state.severities[entry.name];
42325
+ return severity !== void 0 && severity !== entry.defaultSeverity ? [[entry.name, severity]] : [];
42326
+ })
42327
+ )
42328
+ ) + "\n"
42329
+ );
42330
+ }
42331
+ function handleProcess(entries2) {
42332
+ return (input, state) => {
42333
+ const filteredEntries = getFilteredEntries(entries2, state.searchText);
42334
+ switch (input.key.name) {
42335
+ case "backspace":
42336
+ return buildState(entries2, 0, state.searchText.slice(0, -1), state.severities).pipe(
42337
+ map6((nextState) => Action2.NextFrame({ state: nextState }))
42338
+ );
42339
+ case "left":
42340
+ if (filteredEntries.length === 0) return succeed6(Action2.Beep());
42341
+ return buildState(entries2, state.startIndex - 1, state.searchText, state.severities).pipe(
42342
+ map6((nextState) => Action2.NextFrame({ state: nextState }))
42343
+ );
42344
+ case "right":
42345
+ if (filteredEntries.length === 0) return succeed6(Action2.Beep());
42346
+ return buildState(entries2, state.startIndex + 1, state.searchText, state.severities).pipe(
42347
+ map6((nextState) => Action2.NextFrame({ state: nextState }))
42348
+ );
42349
+ case "up":
42350
+ case "down":
42351
+ if (filteredEntries.length === 0) return succeed6(Action2.Beep());
42352
+ return buildState(entries2, state.startIndex, state.searchText, {
42353
+ ...state.severities,
42354
+ [filteredEntries[state.startIndex].name]: cycleSeverity(
42355
+ state.severities[filteredEntries[state.startIndex].name] ?? filteredEntries[state.startIndex].defaultSeverity,
42356
+ input.key.name === "up" ? "left" : "right"
42357
+ )
42358
+ }).pipe(map6((nextState) => Action2.NextFrame({ state: nextState })));
42359
+ case "enter":
42360
+ case "return":
42361
+ return succeed6(Action2.Submit({
42362
+ value: Object.fromEntries(
42363
+ entries2.flatMap((entry) => {
42364
+ const severity = state.severities[entry.name];
42365
+ return severity !== void 0 && severity !== entry.defaultSeverity ? [[entry.name, severity]] : [];
42366
+ })
42367
+ )
42368
+ }));
42369
+ default:
42370
+ if (!isPrintableInput(input)) return succeed6(Action2.Beep());
42371
+ return buildState(entries2, 0, state.searchText + input.input, state.severities).pipe(
42372
+ map6((nextState) => Action2.NextFrame({ state: nextState }))
42373
+ );
42374
+ }
40693
42375
  };
40694
42376
  }
42377
+ function getPromptEntries(diagnostics3) {
42378
+ const diagnosticsByName = new Map(diagnostics3.map((diagnostic) => [diagnostic.name, diagnostic]));
42379
+ return diagnostics3.flatMap((rule) => {
42380
+ const diagnostic = diagnosticsByName.get(rule.name);
42381
+ if (!diagnostic) {
42382
+ return [];
42383
+ }
42384
+ return [{
42385
+ name: rule.name,
42386
+ group: diagnostic.group,
42387
+ description: diagnostic.description,
42388
+ previewSourceText: diagnostic.preview.sourceText,
42389
+ previewDiagnostics: diagnostic.preview.diagnostics,
42390
+ defaultSeverity: diagnostic.defaultSeverity
42391
+ }];
42392
+ });
42393
+ }
40695
42394
  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)
42395
+ const entries2 = getPromptEntries(diagnostics3);
42396
+ return custom(buildState(entries2, 0, "", initialSeverities), {
42397
+ render: (state, action2) => {
42398
+ switch (action2._tag) {
42399
+ case "Beep":
42400
+ return succeed6(BEEP);
42401
+ case "NextFrame":
42402
+ return succeed6(action2.state.renderedLines.join("\n"));
42403
+ case "Submit":
42404
+ return renderSubmission(state, entries2);
42405
+ }
42406
+ },
42407
+ process: handleProcess(entries2),
42408
+ clear: (state) => gen2(function* () {
42409
+ const terminal = yield* Terminal;
42410
+ const columns = yield* terminal.columns;
42411
+ return eraseRenderedLines(state.renderedLines, columns);
42412
+ })
40709
42413
  });
40710
42414
  }
40711
42415