@effect/language-service 0.79.0 → 0.81.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -25719,7 +25719,7 @@ var runWith2 = (command, config) => {
25719
25719
  // package.json
25720
25720
  var package_default = {
25721
25721
  name: "@effect/language-service",
25722
- version: "0.79.0",
25722
+ version: "0.81.0",
25723
25723
  publishConfig: {
25724
25724
  access: "public",
25725
25725
  directory: "dist"
@@ -25747,13 +25747,13 @@ var package_default = {
25747
25747
  ],
25748
25748
  scripts: {
25749
25749
  build: "tsup",
25750
- codegen: "node --import tsx ./scripts/generate-diagnostics-readme-table.ts",
25750
+ codegen: "node --import tsx ./scripts/codegen.ts",
25751
25751
  dev: "tsup --watch",
25752
25752
  clean: "rimraf dist build .tsbuildinfo",
25753
25753
  lint: "eslint src test",
25754
25754
  "lint-fix": "eslint src test --fix",
25755
25755
  check: "tsc -b tsconfig.json",
25756
- "check:codegen": "node --import tsx ./scripts/generate-diagnostics-readme-table.ts --check",
25756
+ "check:codegen": "node --import tsx ./scripts/codegen.ts --check",
25757
25757
  circular: "madge --extensions ts --circular --no-color --no-spinner --warning src",
25758
25758
  test: "vitest",
25759
25759
  "test-update": "vitest --update",
@@ -27536,6 +27536,145 @@ var defaults = {
27536
27536
  mermaidProvider: "mermaid.live",
27537
27537
  skipDisabledOptimization: false
27538
27538
  };
27539
+ var booleanSchema = (description, defaultValue) => ({
27540
+ type: "boolean",
27541
+ description,
27542
+ default: defaultValue
27543
+ });
27544
+ var stringArraySchema = (description, defaultValue) => ({
27545
+ type: "array",
27546
+ description,
27547
+ default: defaultValue,
27548
+ items: { type: "string" }
27549
+ });
27550
+ var stringEnumSchema = (description, values2, defaultValue) => ({
27551
+ type: "string",
27552
+ description,
27553
+ enum: values2,
27554
+ default: defaultValue
27555
+ });
27556
+ var languageServicePluginAdditionalPropertiesJsonSchema = {
27557
+ refactors: booleanSchema("Controls Effect refactors.", defaults.refactors),
27558
+ diagnostics: booleanSchema("Controls Effect diagnostics.", defaults.diagnostics),
27559
+ diagnosticsName: booleanSchema(
27560
+ "Controls whether to include the rule name in diagnostic messages.",
27561
+ defaults.diagnosticsName
27562
+ ),
27563
+ missingDiagnosticNextLine: stringEnumSchema(
27564
+ "Controls the severity of warnings for unused @effect-diagnostics-next-line comments.",
27565
+ ["off", "error", "warning", "message", "suggestion"],
27566
+ defaults.missingDiagnosticNextLine
27567
+ ),
27568
+ includeSuggestionsInTsc: booleanSchema(
27569
+ "When patch mode is enabled, reports suggestion diagnostics as messages in TSC with a [suggestion] prefix.",
27570
+ defaults.includeSuggestionsInTsc
27571
+ ),
27572
+ ignoreEffectWarningsInTscExitCode: booleanSchema(
27573
+ "When enabled, Effect warnings do not affect the patched tsc exit code.",
27574
+ defaults.ignoreEffectWarningsInTscExitCode
27575
+ ),
27576
+ ignoreEffectErrorsInTscExitCode: booleanSchema(
27577
+ "When enabled, Effect errors do not affect the patched tsc exit code.",
27578
+ defaults.ignoreEffectErrorsInTscExitCode
27579
+ ),
27580
+ ignoreEffectSuggestionsInTscExitCode: booleanSchema(
27581
+ "When enabled, Effect suggestions do not affect the patched tsc exit code.",
27582
+ defaults.ignoreEffectSuggestionsInTscExitCode
27583
+ ),
27584
+ quickinfoEffectParameters: stringEnumSchema(
27585
+ "Controls when Effect quickinfo should include full type parameters.",
27586
+ ["always", "never", "whentruncated"],
27587
+ defaults.quickinfoEffectParameters
27588
+ ),
27589
+ quickinfo: booleanSchema("Controls Effect quickinfo.", defaults.quickinfo),
27590
+ quickinfoMaximumLength: {
27591
+ type: "number",
27592
+ description: "Controls the maximum quickinfo length. Use -1 to disable truncation.",
27593
+ default: defaults.quickinfoMaximumLength
27594
+ },
27595
+ keyPatterns: {
27596
+ type: "array",
27597
+ description: "Configures key patterns used for generated Effect service and error keys.",
27598
+ default: defaults.keyPatterns,
27599
+ items: {
27600
+ type: "object",
27601
+ properties: {
27602
+ target: stringEnumSchema("The key builder target.", ["service", "error", "custom"], "service"),
27603
+ pattern: stringEnumSchema(
27604
+ "The key generation pattern.",
27605
+ ["package-identifier", "default", "default-hashed"],
27606
+ "default"
27607
+ ),
27608
+ skipLeadingPath: stringArraySchema("Path prefixes to strip before generating keys.", ["src/"])
27609
+ }
27610
+ }
27611
+ },
27612
+ extendedKeyDetection: booleanSchema(
27613
+ "Enables extended heuristics when detecting key sources.",
27614
+ defaults.extendedKeyDetection
27615
+ ),
27616
+ completions: booleanSchema("Controls Effect completions.", defaults.completions),
27617
+ goto: booleanSchema("Controls Effect goto references support.", defaults.goto),
27618
+ inlays: booleanSchema("Controls Effect inlay hints.", defaults.inlays),
27619
+ allowedDuplicatedPackages: stringArraySchema(
27620
+ "Package names that are allowed to duplicate Effect as a peer dependency.",
27621
+ defaults.allowedDuplicatedPackages
27622
+ ),
27623
+ namespaceImportPackages: stringArraySchema(
27624
+ "Package names that should prefer namespace imports.",
27625
+ defaults.namespaceImportPackages
27626
+ ),
27627
+ topLevelNamedReexports: stringEnumSchema(
27628
+ "For namespaceImportPackages, controls how top-level named re-exports are handled.",
27629
+ ["ignore", "follow"],
27630
+ defaults.topLevelNamedReexports
27631
+ ),
27632
+ barrelImportPackages: stringArraySchema(
27633
+ "Package names that should prefer imports from their top-level barrel file.",
27634
+ defaults.barrelImportPackages
27635
+ ),
27636
+ importAliases: {
27637
+ type: "object",
27638
+ description: "Custom aliases to use for imported identifiers.",
27639
+ default: defaults.importAliases,
27640
+ additionalProperties: {
27641
+ type: "string"
27642
+ }
27643
+ },
27644
+ renames: booleanSchema("Controls Effect rename helpers.", defaults.renames),
27645
+ noExternal: booleanSchema(
27646
+ "Disables features that link to external websites.",
27647
+ defaults.noExternal
27648
+ ),
27649
+ pipeableMinArgCount: {
27650
+ type: "number",
27651
+ description: "Minimum argument count required before pipeable suggestions are emitted.",
27652
+ default: defaults.pipeableMinArgCount
27653
+ },
27654
+ effectFn: {
27655
+ type: "array",
27656
+ description: "Configures which Effect.fn variants should be suggested.",
27657
+ default: defaults.effectFn,
27658
+ items: {
27659
+ type: "string",
27660
+ enum: ["untraced", "span", "suggested-span", "inferred-span", "no-span"]
27661
+ }
27662
+ },
27663
+ layerGraphFollowDepth: {
27664
+ type: "number",
27665
+ description: "Controls how deeply layer graph analysis follows dependencies.",
27666
+ default: defaults.layerGraphFollowDepth
27667
+ },
27668
+ mermaidProvider: {
27669
+ type: "string",
27670
+ description: "Controls which Mermaid renderer is used for layer graphs.",
27671
+ default: defaults.mermaidProvider
27672
+ },
27673
+ skipDisabledOptimization: booleanSchema(
27674
+ "When enabled, disabled diagnostics are still processed so comment-based overrides can be honored.",
27675
+ defaults.skipDisabledOptimization
27676
+ )
27677
+ };
27539
27678
  function parseKeyPatterns(patterns) {
27540
27679
  const result3 = [];
27541
27680
  for (const entry of patterns) {
@@ -31661,6 +31800,7 @@ var anyUnknownInErrorContext = createDiagnostic({
31661
31800
  name: "anyUnknownInErrorContext",
31662
31801
  code: 28,
31663
31802
  description: "Detects 'any' or 'unknown' types in Effect error or requirements channels",
31803
+ group: "correctness",
31664
31804
  severity: "off",
31665
31805
  fixable: false,
31666
31806
  supportedEffect: ["v3", "v4"],
@@ -31766,6 +31906,7 @@ var catchAllToMapError = createDiagnostic({
31766
31906
  name: "catchAllToMapError",
31767
31907
  code: 39,
31768
31908
  description: "Suggests using Effect.mapError instead of Effect.catchAll when the callback only wraps the error with Effect.fail",
31909
+ group: "style",
31769
31910
  severity: "suggestion",
31770
31911
  fixable: true,
31771
31912
  supportedEffect: ["v3", "v4"],
@@ -31865,6 +32006,7 @@ var catchUnfailableEffect = createDiagnostic({
31865
32006
  name: "catchUnfailableEffect",
31866
32007
  code: 2,
31867
32008
  description: "Warns when using error handling on Effects that never fail (error type is 'never')",
32009
+ group: "antipattern",
31868
32010
  severity: "suggestion",
31869
32011
  fixable: false,
31870
32012
  supportedEffect: ["v3", "v4"],
@@ -31916,6 +32058,7 @@ var classSelfMismatch = createDiagnostic({
31916
32058
  name: "classSelfMismatch",
31917
32059
  code: 20,
31918
32060
  description: "Ensures Self type parameter matches the class name in Service/Tag/Schema classes",
32061
+ group: "correctness",
31919
32062
  severity: "error",
31920
32063
  fixable: true,
31921
32064
  supportedEffect: ["v3", "v4"],
@@ -32056,6 +32199,7 @@ var deterministicKeys = createDiagnostic({
32056
32199
  name: "deterministicKeys",
32057
32200
  code: 25,
32058
32201
  description: "Enforces deterministic naming for service/tag/error identifiers based on class names",
32202
+ group: "style",
32059
32203
  severity: "off",
32060
32204
  fixable: true,
32061
32205
  supportedEffect: ["v3", "v4"],
@@ -32175,6 +32319,7 @@ var duplicatePackage = createDiagnostic({
32175
32319
  name: "duplicatePackage",
32176
32320
  code: 6,
32177
32321
  description: "Detects when multiple versions of the same Effect package are loaded",
32322
+ group: "correctness",
32178
32323
  severity: "warning",
32179
32324
  fixable: false,
32180
32325
  supportedEffect: ["v3", "v4"],
@@ -32206,6 +32351,7 @@ var effectFnIife = createDiagnostic({
32206
32351
  name: "effectFnIife",
32207
32352
  code: 46,
32208
32353
  description: "Effect.fn or Effect.fnUntraced is called as an IIFE (Immediately Invoked Function Expression). Use Effect.gen instead.",
32354
+ group: "antipattern",
32209
32355
  severity: "warning",
32210
32356
  fixable: true,
32211
32357
  supportedEffect: ["v3", "v4"],
@@ -32310,6 +32456,7 @@ var effectFnOpportunity = createDiagnostic({
32310
32456
  name: "effectFnOpportunity",
32311
32457
  code: 41,
32312
32458
  description: "Suggests using Effect.fn for functions that returns an Effect",
32459
+ group: "style",
32313
32460
  severity: "suggestion",
32314
32461
  fixable: true,
32315
32462
  supportedEffect: ["v3", "v4"],
@@ -32904,6 +33051,7 @@ var effectGenUsesAdapter = createDiagnostic({
32904
33051
  name: "effectGenUsesAdapter",
32905
33052
  code: 23,
32906
33053
  description: "Warns when using the deprecated adapter parameter in Effect.gen",
33054
+ group: "antipattern",
32907
33055
  severity: "warning",
32908
33056
  fixable: false,
32909
33057
  supportedEffect: ["v3", "v4"],
@@ -32944,6 +33092,7 @@ var effectInFailure = createDiagnostic({
32944
33092
  name: "effectInFailure",
32945
33093
  code: 49,
32946
33094
  description: "Warns when an Effect is used inside an Effect failure channel",
33095
+ group: "antipattern",
32947
33096
  severity: "warning",
32948
33097
  fixable: false,
32949
33098
  supportedEffect: ["v3", "v4"],
@@ -33010,6 +33159,7 @@ var effectInVoidSuccess = createDiagnostic({
33010
33159
  name: "effectInVoidSuccess",
33011
33160
  code: 14,
33012
33161
  description: "Detects nested Effects in void success channels that may cause unexecuted effects",
33162
+ group: "antipattern",
33013
33163
  severity: "warning",
33014
33164
  fixable: false,
33015
33165
  supportedEffect: ["v3", "v4"],
@@ -33061,6 +33211,7 @@ var effectMapVoid = createDiagnostic({
33061
33211
  name: "effectMapVoid",
33062
33212
  code: 40,
33063
33213
  description: "Suggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {})",
33214
+ group: "style",
33064
33215
  severity: "suggestion",
33065
33216
  fixable: true,
33066
33217
  supportedEffect: ["v3", "v4"],
@@ -33127,6 +33278,7 @@ var effectSucceedWithVoid = createDiagnostic({
33127
33278
  name: "effectSucceedWithVoid",
33128
33279
  code: 47,
33129
33280
  description: "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
33281
+ group: "style",
33130
33282
  severity: "suggestion",
33131
33283
  fixable: true,
33132
33284
  supportedEffect: ["v3", "v4"],
@@ -33180,6 +33332,7 @@ var extendsNativeError = createDiagnostic({
33180
33332
  name: "extendsNativeError",
33181
33333
  code: 50,
33182
33334
  description: "Warns when a class directly extends the native Error class",
33335
+ group: "effectNative",
33183
33336
  severity: "off",
33184
33337
  fixable: false,
33185
33338
  supportedEffect: ["v3", "v4"],
@@ -33232,6 +33385,7 @@ var floatingEffect = createDiagnostic({
33232
33385
  name: "floatingEffect",
33233
33386
  code: 3,
33234
33387
  description: "Ensures Effects are yielded or assigned to variables, not left floating",
33388
+ group: "correctness",
33235
33389
  severity: "error",
33236
33390
  fixable: false,
33237
33391
  supportedEffect: ["v3", "v4"],
@@ -33285,6 +33439,7 @@ var genericEffectServices = createDiagnostic({
33285
33439
  name: "genericEffectServices",
33286
33440
  code: 10,
33287
33441
  description: "Prevents services with type parameters that cannot be discriminated at runtime",
33442
+ group: "correctness",
33288
33443
  severity: "warning",
33289
33444
  fixable: false,
33290
33445
  supportedEffect: ["v3", "v4"],
@@ -33334,6 +33489,7 @@ var globalErrorInEffectCatch = createDiagnostic({
33334
33489
  name: "globalErrorInEffectCatch",
33335
33490
  code: 36,
33336
33491
  description: "Warns when catch callbacks return global Error type instead of typed errors",
33492
+ group: "antipattern",
33337
33493
  severity: "warning",
33338
33494
  fixable: false,
33339
33495
  supportedEffect: ["v3", "v4"],
@@ -33396,6 +33552,7 @@ var globalErrorInEffectFailure = createDiagnostic({
33396
33552
  name: "globalErrorInEffectFailure",
33397
33553
  code: 35,
33398
33554
  description: "Warns when the global Error type is used in an Effect failure channel",
33555
+ group: "antipattern",
33399
33556
  severity: "warning",
33400
33557
  fixable: false,
33401
33558
  supportedEffect: ["v3", "v4"],
@@ -33446,11 +33603,54 @@ var globalErrorInEffectFailure = createDiagnostic({
33446
33603
  })
33447
33604
  });
33448
33605
 
33606
+ // src/diagnostics/globalFetch.ts
33607
+ var globalFetch = createDiagnostic({
33608
+ name: "globalFetch",
33609
+ code: 53,
33610
+ description: "Warns when using the global fetch function instead of the Effect HTTP client",
33611
+ group: "effectNative",
33612
+ severity: "off",
33613
+ fixable: false,
33614
+ supportedEffect: ["v3", "v4"],
33615
+ apply: fn3("globalFetch.apply")(function* (sourceFile, report) {
33616
+ const ts = yield* service2(TypeScriptApi);
33617
+ const typeChecker = yield* service2(TypeCheckerApi);
33618
+ const typeParser = yield* service2(TypeParser);
33619
+ const fetchSymbol = typeChecker.resolveName("fetch", void 0, ts.SymbolFlags.Value, false);
33620
+ if (!fetchSymbol) return;
33621
+ const effectVersion = typeParser.supportedEffect();
33622
+ const packageName = effectVersion === "v3" ? "@effect/platform" : "effect/unstable/http";
33623
+ const messageText = `Prefer using HttpClient from ${packageName} instead of the global 'fetch' function.`;
33624
+ const nodeToVisit = [];
33625
+ const appendNodeToVisit = (node) => {
33626
+ nodeToVisit.push(node);
33627
+ return void 0;
33628
+ };
33629
+ ts.forEachChild(sourceFile, appendNodeToVisit);
33630
+ while (nodeToVisit.length > 0) {
33631
+ const node = nodeToVisit.shift();
33632
+ if (ts.isCallExpression(node)) {
33633
+ const symbol4 = typeChecker.getSymbolAtLocation(node.expression);
33634
+ const resolvedSymbol = symbol4 && symbol4.flags & ts.SymbolFlags.Alias ? typeChecker.getAliasedSymbol(symbol4) : symbol4;
33635
+ if (resolvedSymbol === fetchSymbol) {
33636
+ report({
33637
+ location: node.expression,
33638
+ messageText,
33639
+ fixes: []
33640
+ });
33641
+ }
33642
+ }
33643
+ ts.forEachChild(node, appendNodeToVisit);
33644
+ }
33645
+ })
33646
+ });
33647
+
33449
33648
  // src/diagnostics/importFromBarrel.ts
33450
33649
  var importFromBarrel = createDiagnostic({
33451
33650
  name: "importFromBarrel",
33452
33651
  code: 12,
33453
33652
  description: "Suggests importing from specific module paths instead of barrel exports",
33653
+ group: "style",
33454
33654
  severity: "off",
33455
33655
  fixable: true,
33456
33656
  supportedEffect: ["v3", "v4"],
@@ -33593,6 +33793,7 @@ var instanceOfSchema = createDiagnostic({
33593
33793
  name: "instanceOfSchema",
33594
33794
  code: 45,
33595
33795
  description: "Suggests using Schema.is instead of instanceof for Effect Schema types",
33796
+ group: "effectNative",
33596
33797
  severity: "off",
33597
33798
  fixable: true,
33598
33799
  supportedEffect: ["v3", "v4"],
@@ -33658,6 +33859,7 @@ var layerMergeAllWithDependencies = createDiagnostic({
33658
33859
  name: "layerMergeAllWithDependencies",
33659
33860
  code: 37,
33660
33861
  description: "Detects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires",
33862
+ group: "antipattern",
33661
33863
  severity: "warning",
33662
33864
  fixable: true,
33663
33865
  supportedEffect: ["v3", "v4"],
@@ -33773,6 +33975,7 @@ var leakingRequirements = createDiagnostic({
33773
33975
  name: "leakingRequirements",
33774
33976
  code: 8,
33775
33977
  description: "Detects implementation services leaked in service methods",
33978
+ group: "antipattern",
33776
33979
  severity: "suggestion",
33777
33980
  fixable: false,
33778
33981
  supportedEffect: ["v3", "v4"],
@@ -33929,6 +34132,7 @@ var missedPipeableOpportunity = createDiagnostic({
33929
34132
  name: "missedPipeableOpportunity",
33930
34133
  code: 26,
33931
34134
  description: "Enforces the use of pipeable style for nested function calls",
34135
+ group: "style",
33932
34136
  severity: "off",
33933
34137
  fixable: true,
33934
34138
  supportedEffect: ["v3", "v4"],
@@ -34111,6 +34315,7 @@ var missingEffectContext = createDiagnostic({
34111
34315
  name: "missingEffectContext",
34112
34316
  code: 1,
34113
34317
  description: "Reports missing service requirements in Effect context channel",
34318
+ group: "correctness",
34114
34319
  severity: "error",
34115
34320
  fixable: false,
34116
34321
  supportedEffect: ["v3", "v4"],
@@ -34162,6 +34367,7 @@ var missingEffectError = createDiagnostic({
34162
34367
  name: "missingEffectError",
34163
34368
  code: 1,
34164
34369
  description: "Reports missing error types in Effect error channel",
34370
+ group: "correctness",
34165
34371
  severity: "error",
34166
34372
  fixable: true,
34167
34373
  supportedEffect: ["v3", "v4"],
@@ -34305,6 +34511,7 @@ var missingEffectServiceDependency = createDiagnostic({
34305
34511
  name: "missingEffectServiceDependency",
34306
34512
  code: 22,
34307
34513
  description: "Checks that Effect.Service dependencies satisfy all required layer inputs",
34514
+ group: "style",
34308
34515
  severity: "off",
34309
34516
  fixable: false,
34310
34517
  supportedEffect: ["v3"],
@@ -34401,6 +34608,7 @@ var missingLayerContext = createDiagnostic({
34401
34608
  name: "missingLayerContext",
34402
34609
  code: 38,
34403
34610
  description: "Reports missing service requirements in Layer context channel",
34611
+ group: "correctness",
34404
34612
  severity: "error",
34405
34613
  fixable: false,
34406
34614
  supportedEffect: ["v3", "v4"],
@@ -34452,6 +34660,7 @@ var missingReturnYieldStar = createDiagnostic({
34452
34660
  name: "missingReturnYieldStar",
34453
34661
  code: 7,
34454
34662
  description: "Suggests using 'return yield*' for Effects with never success for better type narrowing",
34663
+ group: "correctness",
34455
34664
  severity: "error",
34456
34665
  fixable: true,
34457
34666
  supportedEffect: ["v3", "v4"],
@@ -34504,6 +34713,7 @@ var missingStarInYieldEffectGen = createDiagnostic({
34504
34713
  name: "missingStarInYieldEffectGen",
34505
34714
  code: 4,
34506
34715
  description: "Enforces using 'yield*' instead of 'yield' when yielding Effects in generators",
34716
+ group: "correctness",
34507
34717
  severity: "error",
34508
34718
  fixable: true,
34509
34719
  supportedEffect: ["v3", "v4"],
@@ -34581,6 +34791,7 @@ var multipleEffectProvide = createDiagnostic({
34581
34791
  name: "multipleEffectProvide",
34582
34792
  code: 18,
34583
34793
  description: "Warns against chaining Effect.provide calls which can cause service lifecycle issues",
34794
+ group: "antipattern",
34584
34795
  severity: "warning",
34585
34796
  fixable: true,
34586
34797
  supportedEffect: ["v3", "v4"],
@@ -34683,7 +34894,11 @@ var moduleAlternativesV3 = /* @__PURE__ */ new Map([
34683
34894
  ["path/win32", { alternative: "Path", module: "path", package: "@effect/platform" }],
34684
34895
  ["node:path/win32", { alternative: "Path", module: "path", package: "@effect/platform" }],
34685
34896
  ["child_process", { alternative: "CommandExecutor", module: "child_process", package: "@effect/platform" }],
34686
- ["node:child_process", { alternative: "CommandExecutor", module: "child_process", package: "@effect/platform" }]
34897
+ ["node:child_process", { alternative: "CommandExecutor", module: "child_process", package: "@effect/platform" }],
34898
+ ["http", { alternative: "HttpClient", module: "http", package: "@effect/platform" }],
34899
+ ["node:http", { alternative: "HttpClient", module: "http", package: "@effect/platform" }],
34900
+ ["https", { alternative: "HttpClient", module: "https", package: "@effect/platform" }],
34901
+ ["node:https", { alternative: "HttpClient", module: "https", package: "@effect/platform" }]
34687
34902
  ]);
34688
34903
  var moduleAlternativesV4 = /* @__PURE__ */ new Map([
34689
34904
  ["fs", { alternative: "FileSystem", module: "fs", package: "effect" }],
@@ -34697,12 +34912,17 @@ var moduleAlternativesV4 = /* @__PURE__ */ new Map([
34697
34912
  ["path/win32", { alternative: "Path", module: "path", package: "effect" }],
34698
34913
  ["node:path/win32", { alternative: "Path", module: "path", package: "effect" }],
34699
34914
  ["child_process", { alternative: "ChildProcess", module: "child_process", package: "effect" }],
34700
- ["node:child_process", { alternative: "ChildProcess", module: "child_process", package: "effect" }]
34915
+ ["node:child_process", { alternative: "ChildProcess", module: "child_process", package: "effect" }],
34916
+ ["http", { alternative: "HttpClient", module: "http", package: "effect/unstable/http" }],
34917
+ ["node:http", { alternative: "HttpClient", module: "http", package: "effect/unstable/http" }],
34918
+ ["https", { alternative: "HttpClient", module: "https", package: "effect/unstable/http" }],
34919
+ ["node:https", { alternative: "HttpClient", module: "https", package: "effect/unstable/http" }]
34701
34920
  ]);
34702
34921
  var nodeBuiltinImport = createDiagnostic({
34703
34922
  name: "nodeBuiltinImport",
34704
34923
  code: 52,
34705
34924
  description: "Warns when importing Node.js built-in modules that have Effect-native counterparts",
34925
+ group: "effectNative",
34706
34926
  severity: "off",
34707
34927
  fixable: false,
34708
34928
  supportedEffect: ["v3", "v4"],
@@ -34746,6 +34966,7 @@ var nonObjectEffectServiceType = createDiagnostic({
34746
34966
  name: "nonObjectEffectServiceType",
34747
34967
  code: 24,
34748
34968
  description: "Ensures Effect.Service types are objects, not primitives",
34969
+ group: "correctness",
34749
34970
  severity: "error",
34750
34971
  fixable: false,
34751
34972
  supportedEffect: ["v3"],
@@ -35530,6 +35751,7 @@ var outdatedApi = createDiagnostic({
35530
35751
  name: "outdatedApi",
35531
35752
  code: 48,
35532
35753
  description: "Detects usage of APIs that have been removed or renamed in Effect v4",
35754
+ group: "correctness",
35533
35755
  severity: "warning",
35534
35756
  fixable: false,
35535
35757
  supportedEffect: ["v4"],
@@ -35599,6 +35821,7 @@ var outdatedEffectCodegen = createDiagnostic({
35599
35821
  name: "outdatedEffectCodegen",
35600
35822
  code: 19,
35601
35823
  description: "Detects when generated code is outdated and needs to be regenerated",
35824
+ group: "correctness",
35602
35825
  severity: "warning",
35603
35826
  fixable: true,
35604
35827
  supportedEffect: ["v3", "v4"],
@@ -35647,6 +35870,7 @@ var overriddenSchemaConstructor = createDiagnostic({
35647
35870
  name: "overriddenSchemaConstructor",
35648
35871
  code: 30,
35649
35872
  description: "Prevents overriding constructors in Schema classes which breaks decoding behavior",
35873
+ group: "correctness",
35650
35874
  severity: "error",
35651
35875
  fixable: true,
35652
35876
  supportedEffect: ["v3", "v4"],
@@ -35786,6 +36010,7 @@ var preferSchemaOverJson = createDiagnostic({
35786
36010
  name: "preferSchemaOverJson",
35787
36011
  code: 44,
35788
36012
  description: "Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify which may throw",
36013
+ group: "effectNative",
35789
36014
  severity: "suggestion",
35790
36015
  fixable: false,
35791
36016
  supportedEffect: ["v3", "v4"],
@@ -35898,6 +36123,7 @@ var redundantSchemaTagIdentifier = createDiagnostic({
35898
36123
  name: "redundantSchemaTagIdentifier",
35899
36124
  code: 42,
35900
36125
  description: "Suggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest",
36126
+ group: "style",
35901
36127
  severity: "suggestion",
35902
36128
  fixable: true,
35903
36129
  supportedEffect: ["v3", "v4"],
@@ -35950,6 +36176,7 @@ var returnEffectInGen = createDiagnostic({
35950
36176
  name: "returnEffectInGen",
35951
36177
  code: 11,
35952
36178
  description: "Warns when returning an Effect in a generator causes nested Effect<Effect<...>>",
36179
+ group: "antipattern",
35953
36180
  severity: "suggestion",
35954
36181
  fixable: true,
35955
36182
  supportedEffect: ["v3", "v4"],
@@ -36021,6 +36248,7 @@ var runEffectInsideEffect = createDiagnostic({
36021
36248
  name: "runEffectInsideEffect",
36022
36249
  code: 32,
36023
36250
  description: "Suggests using Runtime methods instead of Effect.run* inside Effect contexts",
36251
+ group: "antipattern",
36024
36252
  severity: "suggestion",
36025
36253
  fixable: true,
36026
36254
  supportedEffect: ["v3"],
@@ -36147,6 +36375,7 @@ var schemaStructWithTag = createDiagnostic({
36147
36375
  name: "schemaStructWithTag",
36148
36376
  code: 34,
36149
36377
  description: "Suggests using Schema.TaggedStruct instead of Schema.Struct with _tag field",
36378
+ group: "style",
36150
36379
  severity: "suggestion",
36151
36380
  fixable: true,
36152
36381
  supportedEffect: ["v3", "v4"],
@@ -36241,9 +36470,10 @@ var schemaSyncInEffect = createDiagnostic({
36241
36470
  name: "schemaSyncInEffect",
36242
36471
  code: 43,
36243
36472
  description: "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
36473
+ group: "antipattern",
36244
36474
  severity: "suggestion",
36245
36475
  fixable: false,
36246
- supportedEffect: ["v3", "v4"],
36476
+ supportedEffect: ["v3"],
36247
36477
  apply: fn3("schemaSyncInEffect.apply")(function* (sourceFile, report) {
36248
36478
  const ts = yield* service2(TypeScriptApi);
36249
36479
  const typeParser = yield* service2(TypeParser);
@@ -36292,6 +36522,7 @@ var schemaUnionOfLiterals = createDiagnostic({
36292
36522
  name: "schemaUnionOfLiterals",
36293
36523
  code: 33,
36294
36524
  description: "Simplifies Schema.Union of multiple Schema.Literal calls into single Schema.Literal",
36525
+ group: "style",
36295
36526
  severity: "off",
36296
36527
  fixable: true,
36297
36528
  supportedEffect: ["v3"],
@@ -36369,6 +36600,7 @@ var scopeInLayerEffect = createDiagnostic({
36369
36600
  name: "scopeInLayerEffect",
36370
36601
  code: 13,
36371
36602
  description: "Suggests using Layer.scoped instead of Layer.effect when Scope is in requirements",
36603
+ group: "antipattern",
36372
36604
  severity: "warning",
36373
36605
  fixable: true,
36374
36606
  supportedEffect: ["v3"],
@@ -36466,6 +36698,7 @@ var serviceNotAsClass = createDiagnostic({
36466
36698
  name: "serviceNotAsClass",
36467
36699
  code: 51,
36468
36700
  description: "Warns when ServiceMap.Service is used as a variable instead of a class declaration",
36701
+ group: "style",
36469
36702
  severity: "off",
36470
36703
  fixable: true,
36471
36704
  supportedEffect: ["v4"],
@@ -36543,6 +36776,7 @@ var strictBooleanExpressions = createDiagnostic({
36543
36776
  name: "strictBooleanExpressions",
36544
36777
  code: 17,
36545
36778
  description: "Enforces boolean types in conditional expressions for type safety",
36779
+ group: "style",
36546
36780
  severity: "off",
36547
36781
  fixable: false,
36548
36782
  supportedEffect: ["v3", "v4"],
@@ -36616,6 +36850,7 @@ var strictEffectProvide = createDiagnostic({
36616
36850
  name: "strictEffectProvide",
36617
36851
  code: 27,
36618
36852
  description: "Warns when using Effect.provide with layers outside of application entry points",
36853
+ group: "antipattern",
36619
36854
  severity: "off",
36620
36855
  fixable: false,
36621
36856
  supportedEffect: ["v3", "v4"],
@@ -36669,6 +36904,7 @@ var tryCatchInEffectGen = createDiagnostic({
36669
36904
  name: "tryCatchInEffectGen",
36670
36905
  code: 15,
36671
36906
  description: "Discourages try/catch in Effect generators in favor of Effect error handling",
36907
+ group: "antipattern",
36672
36908
  severity: "suggestion",
36673
36909
  fixable: false,
36674
36910
  supportedEffect: ["v3", "v4"],
@@ -36728,6 +36964,7 @@ var unknownInEffectCatch = createDiagnostic({
36728
36964
  name: "unknownInEffectCatch",
36729
36965
  code: 31,
36730
36966
  description: "Warns when catch callbacks return unknown instead of typed errors",
36967
+ group: "antipattern",
36731
36968
  severity: "warning",
36732
36969
  fixable: false,
36733
36970
  supportedEffect: ["v3", "v4"],
@@ -36791,6 +37028,7 @@ var unnecessaryEffectGen = createDiagnostic({
36791
37028
  name: "unnecessaryEffectGen",
36792
37029
  code: 5,
36793
37030
  description: "Suggests removing Effect.gen when it contains only a single return statement",
37031
+ group: "style",
36794
37032
  severity: "suggestion",
36795
37033
  fixable: true,
36796
37034
  supportedEffect: ["v3", "v4"],
@@ -36837,6 +37075,7 @@ var unnecessaryFailYieldableError = createDiagnostic({
36837
37075
  name: "unnecessaryFailYieldableError",
36838
37076
  code: 29,
36839
37077
  description: "Suggests yielding yieldable errors directly instead of wrapping with Effect.fail",
37078
+ group: "style",
36840
37079
  severity: "suggestion",
36841
37080
  fixable: true,
36842
37081
  supportedEffect: ["v3", "v4"],
@@ -36898,6 +37137,7 @@ var unnecessaryPipe = createDiagnostic({
36898
37137
  name: "unnecessaryPipe",
36899
37138
  code: 9,
36900
37139
  description: "Removes pipe calls with no arguments",
37140
+ group: "style",
36901
37141
  severity: "suggestion",
36902
37142
  fixable: true,
36903
37143
  supportedEffect: ["v3", "v4"],
@@ -36946,6 +37186,7 @@ var unnecessaryPipeChain = createDiagnostic({
36946
37186
  name: "unnecessaryPipeChain",
36947
37187
  code: 16,
36948
37188
  description: "Simplifies chained pipe calls into a single pipe call",
37189
+ group: "style",
36949
37190
  severity: "suggestion",
36950
37191
  fixable: true,
36951
37192
  supportedEffect: ["v3", "v4"],
@@ -37023,6 +37264,7 @@ var unsupportedServiceAccessors = createDiagnostic({
37023
37264
  name: "unsupportedServiceAccessors",
37024
37265
  code: 21,
37025
37266
  description: "Warns about service accessors that need codegen due to generic/complex signatures",
37267
+ group: "correctness",
37026
37268
  severity: "warning",
37027
37269
  fixable: true,
37028
37270
  supportedEffect: ["v3", "v4"],
@@ -37100,6 +37342,7 @@ var diagnostics = [
37100
37342
  leakingRequirements,
37101
37343
  unnecessaryPipe,
37102
37344
  genericEffectServices,
37345
+ globalFetch,
37103
37346
  returnEffectInGen,
37104
37347
  tryCatchInEffectGen,
37105
37348
  importFromBarrel,
@@ -37145,6 +37388,11 @@ var DiagnosticsFoundError = class extends TaggedError2("DiagnosticsFoundError")
37145
37388
  return `Found ${this.errorsCount} errors, ${this.warningsCount} warnings and ${this.messagesCount} messages.`;
37146
37389
  }
37147
37390
  };
37391
+ var InvalidLspConfigError = class extends TaggedError2("InvalidLspConfigError") {
37392
+ get message() {
37393
+ return `Invalid JSON lsp config: ${this.lspconfig}`;
37394
+ }
37395
+ };
37148
37396
  var categoryToSeverity = (category, tsInstance) => {
37149
37397
  switch (category) {
37150
37398
  case tsInstance.DiagnosticCategory.Error:
@@ -37340,9 +37588,15 @@ var diagnostics2 = Command_exports.make(
37340
37588
  progress: Flag_exports.boolean("progress").pipe(
37341
37589
  Flag_exports.withDefault(false),
37342
37590
  Flag_exports.withDescription("Show progress as files are checked (outputs to stderr)")
37591
+ ),
37592
+ lspconfig: Flag_exports.string("lspconfig").pipe(
37593
+ Flag_exports.optional,
37594
+ Flag_exports.withDescription(
37595
+ `An optional inline JSON lsp config that replaces the current project lsp config. e.g. '{ "effectFn": ["untraced"] }'`
37596
+ )
37343
37597
  )
37344
37598
  },
37345
- fn2("diagnostics")(function* ({ file: file4, format: format3, progress, project: project2, severity, strict }) {
37599
+ fn2("diagnostics")(function* ({ file: file4, format: format3, lspconfig, progress, project: project2, severity, strict }) {
37346
37600
  const path4 = yield* Path;
37347
37601
  const severityFilter = parseSeverityFilter(severity);
37348
37602
  const state = {
@@ -37406,7 +37660,14 @@ var diagnostics2 = Command_exports.make(
37406
37660
  if (!program) continue;
37407
37661
  const sourceFile = program.getSourceFile(filePath);
37408
37662
  if (!sourceFile) continue;
37409
- const pluginConfig = extractEffectLspOptions(program.getCompilerOptions());
37663
+ let pluginConfig = extractEffectLspOptions(program.getCompilerOptions());
37664
+ if (isSome2(lspconfig)) {
37665
+ try {
37666
+ pluginConfig = { name: "@effect/language-service", ...JSON.parse(lspconfig.value) };
37667
+ } catch {
37668
+ return yield* new InvalidLspConfigError({ lspconfig: lspconfig.value });
37669
+ }
37670
+ }
37410
37671
  if (!pluginConfig) continue;
37411
37672
  const rawResults = pipe(
37412
37673
  getSemanticDiagnosticsWithCodeFixes(diagnostics, sourceFile),
@@ -38044,13 +38305,6 @@ var GREEN = "\x1B[0;32m";
38044
38305
  var YELLOW = "\x1B[0;33m";
38045
38306
  var BLUE = "\x1B[0;34m";
38046
38307
  var CYAN = "\x1B[0;36m";
38047
- var WHITE = "\x1B[0;37m";
38048
- var CYAN_BRIGHT = "\x1B[0;96m";
38049
- var BG_BLACK_BRIGHT = "\x1B[0;100m";
38050
- var BG_RED = "\x1B[41m";
38051
- var BG_YELLOW = "\x1B[43m";
38052
- var BG_BLUE = "\x1B[0;44m";
38053
- var BG_CYAN = "\x1B[0;46m";
38054
38308
  var ansi = (text2, code) => `${code}${text2}${RESET}`;
38055
38309
  var ERASE_LINE = "\x1B[2K";
38056
38310
  var CURSOR_LEFT = "\r";
@@ -38058,6 +38312,11 @@ var CURSOR_HIDE = "\x1B[?25l";
38058
38312
  var CURSOR_SHOW = "\x1B[?25h";
38059
38313
  var CURSOR_TO_0 = "\x1B[G";
38060
38314
  var BEEP = "\x07";
38315
+ var ITALIC = "\x1B[0;3m";
38316
+ var UNDERLINE = "\x1B[0;4m";
38317
+ var ANSI_ESCAPE_REGEX = new RegExp(String.raw`\u001b\[[0-?]*[ -/]*[@-~]`, "g");
38318
+ var stripAnsi2 = (text2) => text2.replace(ANSI_ESCAPE_REGEX, "");
38319
+ var visibleLength = (text2) => stripAnsi2(text2).length;
38061
38320
 
38062
38321
  // src/cli/overview.ts
38063
38322
  var import_project_service3 = __toESM(require_dist3());
@@ -39690,6 +39949,7 @@ var assess = (input) => gen2(function* () {
39690
39949
  });
39691
39950
 
39692
39951
  // src/cli/setup/changes.ts
39952
+ var TSCONFIG_SCHEMA_URL = "https://raw.githubusercontent.com/Effect-TS/language-service/refs/heads/main/schema.json";
39693
39953
  function emptyFileChangesResult() {
39694
39954
  return {
39695
39955
  codeActions: [],
@@ -40020,9 +40280,100 @@ var computeTsConfigChanges = (current, target, lspVersion) => {
40020
40280
  if (!rootObj) {
40021
40281
  return emptyFileChangesResult();
40022
40282
  }
40283
+ const buildPluginObject = (severities) => {
40284
+ const nameProperty = ts.factory.createPropertyAssignment(
40285
+ ts.factory.createStringLiteral("name"),
40286
+ ts.factory.createStringLiteral("@effect/language-service")
40287
+ );
40288
+ return match(severities, {
40289
+ onNone: () => {
40290
+ return ts.factory.createObjectLiteralExpression([nameProperty], false);
40291
+ },
40292
+ onSome: (sevs) => {
40293
+ const severityProperties = Object.entries(sevs).map(
40294
+ ([name, severity]) => ts.factory.createPropertyAssignment(
40295
+ ts.factory.createStringLiteral(name),
40296
+ ts.factory.createStringLiteral(severity)
40297
+ )
40298
+ );
40299
+ const diagnosticSeverityProperty = ts.factory.createPropertyAssignment(
40300
+ ts.factory.createStringLiteral("diagnosticSeverity"),
40301
+ ts.factory.createObjectLiteralExpression(severityProperties, true)
40302
+ );
40303
+ return ts.factory.createObjectLiteralExpression(
40304
+ [nameProperty, diagnosticSeverityProperty],
40305
+ true
40306
+ );
40307
+ }
40308
+ });
40309
+ };
40310
+ const schemaPropertyAssignment = ts.factory.createPropertyAssignment(
40311
+ ts.factory.createStringLiteral("$schema"),
40312
+ ts.factory.createStringLiteral(TSCONFIG_SCHEMA_URL)
40313
+ );
40023
40314
  const compilerOptionsProperty = findPropertyInObject(ts, rootObj, "compilerOptions");
40024
40315
  if (!compilerOptionsProperty) {
40025
- return emptyFileChangesResult();
40316
+ if (isNone2(lspVersion)) {
40317
+ return emptyFileChangesResult();
40318
+ }
40319
+ const textChanges2 = ts.textChanges;
40320
+ const host2 = createMinimalHost(ts);
40321
+ const formatOptions2 = { indentSize: 2, tabSize: 2 };
40322
+ const formatContext2 = ts.formatting.getFormatContext(formatOptions2, host2);
40323
+ const preferences2 = {};
40324
+ const fileChanges2 = textChanges2.ChangeTracker.with(
40325
+ { host: host2, formatContext: formatContext2, preferences: preferences2 },
40326
+ (tracker) => {
40327
+ const schemaProperty = findPropertyInObject(ts, rootObj, "$schema");
40328
+ const shouldAddSchema = !schemaProperty;
40329
+ const shouldUpdateSchema = !!schemaProperty && (!ts.isStringLiteral(schemaProperty.initializer) || schemaProperty.initializer.text !== TSCONFIG_SCHEMA_URL);
40330
+ if (shouldAddSchema) {
40331
+ descriptions.push("Add $schema to tsconfig");
40332
+ } else if (shouldUpdateSchema) {
40333
+ descriptions.push("Update $schema in tsconfig");
40334
+ }
40335
+ descriptions.push("Add compilerOptions with @effect/language-service plugin");
40336
+ const compilerOptionsAssignment = ts.factory.createPropertyAssignment(
40337
+ ts.factory.createStringLiteral("compilerOptions"),
40338
+ ts.factory.createObjectLiteralExpression([
40339
+ ts.factory.createPropertyAssignment(
40340
+ ts.factory.createStringLiteral("plugins"),
40341
+ ts.factory.createArrayLiteralExpression([buildPluginObject(target.diagnosticSeverities)], true)
40342
+ )
40343
+ ], true)
40344
+ );
40345
+ const nextProperties = rootObj.properties.flatMap((property) => {
40346
+ if (schemaProperty && property === schemaProperty) {
40347
+ return [schemaPropertyAssignment];
40348
+ }
40349
+ return [property];
40350
+ });
40351
+ if (shouldAddSchema) {
40352
+ nextProperties.push(schemaPropertyAssignment);
40353
+ }
40354
+ nextProperties.push(compilerOptionsAssignment);
40355
+ tracker.replaceNode(
40356
+ current.sourceFile,
40357
+ rootObj,
40358
+ ts.factory.createObjectLiteralExpression(nextProperties, true)
40359
+ );
40360
+ }
40361
+ );
40362
+ const fileChange2 = fileChanges2.find((fc) => fc.fileName === current.path);
40363
+ const changes2 = fileChange2 ? fileChange2.textChanges : [];
40364
+ if (changes2.length === 0) {
40365
+ return { codeActions: [], messages };
40366
+ }
40367
+ return {
40368
+ codeActions: [{
40369
+ description: descriptions.join("; "),
40370
+ changes: [{
40371
+ fileName: current.path,
40372
+ textChanges: changes2
40373
+ }]
40374
+ }],
40375
+ messages
40376
+ };
40026
40377
  }
40027
40378
  if (!ts.isObjectLiteralExpression(compilerOptionsProperty.initializer)) {
40028
40379
  return emptyFileChangesResult();
@@ -40036,8 +40387,13 @@ var computeTsConfigChanges = (current, target, lspVersion) => {
40036
40387
  const fileChanges = textChanges.ChangeTracker.with(
40037
40388
  { host, formatContext, preferences },
40038
40389
  (tracker) => {
40390
+ const schemaProperty = findPropertyInObject(ts, rootObj, "$schema");
40039
40391
  const pluginsProperty = findPropertyInObject(ts, compilerOptions, "plugins");
40040
40392
  if (isNone2(lspVersion)) {
40393
+ if (schemaProperty) {
40394
+ descriptions.push("Remove $schema from tsconfig");
40395
+ deleteNodeFromList(tracker, current.sourceFile, rootObj.properties, schemaProperty);
40396
+ }
40041
40397
  if (pluginsProperty && ts.isArrayLiteralExpression(pluginsProperty.initializer)) {
40042
40398
  const pluginsArray = pluginsProperty.initializer;
40043
40399
  const lspPluginElement = pluginsArray.elements.find((element) => {
@@ -40055,33 +40411,13 @@ var computeTsConfigChanges = (current, target, lspVersion) => {
40055
40411
  }
40056
40412
  }
40057
40413
  } else {
40058
- const buildPluginObject = (severities) => {
40059
- const nameProperty = ts.factory.createPropertyAssignment(
40060
- ts.factory.createStringLiteral("name"),
40061
- ts.factory.createStringLiteral("@effect/language-service")
40062
- );
40063
- return match(severities, {
40064
- onNone: () => {
40065
- return ts.factory.createObjectLiteralExpression([nameProperty], false);
40066
- },
40067
- onSome: (sevs) => {
40068
- const severityProperties = Object.entries(sevs).map(
40069
- ([name, severity]) => ts.factory.createPropertyAssignment(
40070
- ts.factory.createStringLiteral(name),
40071
- ts.factory.createStringLiteral(severity)
40072
- )
40073
- );
40074
- const diagnosticSeverityProperty = ts.factory.createPropertyAssignment(
40075
- ts.factory.createStringLiteral("diagnosticSeverity"),
40076
- ts.factory.createObjectLiteralExpression(severityProperties, true)
40077
- );
40078
- return ts.factory.createObjectLiteralExpression(
40079
- [nameProperty, diagnosticSeverityProperty],
40080
- true
40081
- );
40082
- }
40083
- });
40084
- };
40414
+ if (!schemaProperty) {
40415
+ descriptions.push("Add $schema to tsconfig");
40416
+ insertNodeAtEndOfList(tracker, current.sourceFile, rootObj.properties, schemaPropertyAssignment);
40417
+ } else if (!ts.isStringLiteral(schemaProperty.initializer) || schemaProperty.initializer.text !== TSCONFIG_SCHEMA_URL) {
40418
+ descriptions.push("Update $schema in tsconfig");
40419
+ tracker.replaceNode(current.sourceFile, schemaProperty.initializer, schemaPropertyAssignment.initializer);
40420
+ }
40085
40421
  const pluginObject = buildPluginObject(target.diagnosticSeverities);
40086
40422
  if (!pluginsProperty) {
40087
40423
  descriptions.push("Add plugins array with @effect/language-service plugin");
@@ -40294,244 +40630,1656 @@ var FileReadError = class extends TaggedError2("FileReadError") {
40294
40630
  }
40295
40631
  };
40296
40632
 
40297
- // src/cli/setup/diagnostic-info.ts
40298
- function getAllDiagnostics() {
40299
- return diagnostics.map((diagnostic) => ({
40300
- name: diagnostic.name,
40301
- code: diagnostic.code,
40302
- defaultSeverity: diagnostic.severity,
40303
- description: diagnostic.description
40304
- }));
40305
- }
40306
- function cycleSeverity(current, direction) {
40307
- const order = ["off", "suggestion", "message", "warning", "error"];
40308
- const currentIndex = order.indexOf(current);
40309
- if (direction === "right") {
40310
- return order[(currentIndex + 1) % order.length];
40311
- } else {
40312
- return order[(currentIndex - 1 + order.length) % order.length];
40313
- }
40314
- }
40315
- var shortNames = {
40316
- off: "off",
40317
- suggestion: "sugg",
40318
- message: "info",
40319
- warning: "warn",
40320
- error: "err"
40321
- };
40322
- var MAX_SEVERITY_LENGTH = Object.values(shortNames).reduce((max2, name) => Math.max(max2, name.length), 0);
40323
- function getSeverityShortName(severity) {
40324
- return shortNames[severity] ?? "???";
40325
- }
40326
-
40327
- // src/cli/setup/diagnostic-prompt.ts
40328
- function eraseLines2(count) {
40329
- let result3 = "";
40330
- for (let i = 0; i < count; i++) {
40331
- if (i > 0) result3 += "\x1B[1A";
40332
- result3 += ERASE_LINE;
40333
- }
40334
- if (count > 0) result3 += CURSOR_LEFT;
40335
- return result3;
40336
- }
40337
- var Action2 = taggedEnum();
40338
- var NEWLINE_REGEX = /\r?\n/;
40339
- function eraseText2(text2, columns) {
40340
- if (columns === 0) {
40341
- return ERASE_LINE + CURSOR_TO_0;
40342
- }
40343
- let rows = 0;
40344
- const lines2 = text2.split(/\r?\n/);
40345
- for (const line of lines2) {
40346
- rows += 1 + Math.floor(Math.max(line.length - 1, 0) / columns);
40347
- }
40348
- return eraseLines2(rows);
40349
- }
40350
- function entriesToDisplay2(cursor, total, maxVisible) {
40351
- const max2 = maxVisible === void 0 ? total : maxVisible;
40352
- let startIndex = Math.min(total - max2, cursor - Math.floor(max2 / 2));
40353
- if (startIndex < 0) {
40354
- startIndex = 0;
40355
- }
40356
- const endIndex = Math.min(startIndex + max2, total);
40357
- return { startIndex, endIndex };
40358
- }
40359
- var defaultFigures2 = {
40360
- arrowUp: "\u2191",
40361
- arrowDown: "\u2193",
40362
- tick: "\u2714",
40363
- pointerSmall: "\u203A"
40364
- };
40365
- var figuresValue = defaultFigures2;
40366
- function getSeverityStyle(severity) {
40367
- const styles = {
40368
- off: WHITE + BG_BLACK_BRIGHT,
40369
- suggestion: WHITE + BG_CYAN,
40370
- message: WHITE + BG_BLUE,
40371
- warning: WHITE + BG_YELLOW,
40372
- error: WHITE + BG_RED
40373
- };
40374
- return styles[severity];
40375
- }
40376
- function renderOutput(leadingSymbol, trailingSymbol, options) {
40377
- const annotateLine2 = (line) => ansi(line, BOLD);
40378
- const prefix = leadingSymbol + " ";
40379
- return match3(options.message.split(NEWLINE_REGEX), {
40380
- onEmpty: () => `${prefix}${trailingSymbol}`,
40381
- onNonEmpty: (promptLines) => {
40382
- const lines2 = map4(promptLines, (line) => annotateLine2(line));
40383
- return `${prefix}${lines2.join("\n ")} ${trailingSymbol} `;
40384
- }
40385
- });
40386
- }
40387
- function renderDiagnostics(state, options, figs, columns) {
40388
- const diagnostics3 = options.diagnostics;
40389
- const toDisplay = entriesToDisplay2(state.index, diagnostics3.length, options.maxPerPage);
40390
- const documents = [];
40391
- for (let index = toDisplay.startIndex; index < toDisplay.endIndex; index++) {
40392
- const diagnostic = diagnostics3[index];
40393
- const isHighlighted = state.index === index;
40394
- const currentSeverity = state.severities[diagnostic.name] ?? diagnostic.defaultSeverity;
40395
- const hasChanged = currentSeverity !== diagnostic.defaultSeverity;
40396
- let prefix = " ";
40397
- if (index === toDisplay.startIndex && toDisplay.startIndex > 0) {
40398
- prefix = figs.arrowUp;
40399
- } else if (index === toDisplay.endIndex - 1 && toDisplay.endIndex < diagnostics3.length) {
40400
- prefix = figs.arrowDown;
40401
- }
40402
- const shortName = getSeverityShortName(currentSeverity);
40403
- const paddedSeverity = shortName.padEnd(MAX_SEVERITY_LENGTH, " ");
40404
- const severityStr = ansi(` ${paddedSeverity} `, getSeverityStyle(currentSeverity));
40405
- const nameText = hasChanged ? `${diagnostic.name}*` : diagnostic.name;
40406
- const nameStr = isHighlighted ? ansi(nameText, CYAN_BRIGHT) : nameText;
40407
- const mainLine = `${prefix} ${severityStr} ${nameStr}`;
40408
- if (isHighlighted && diagnostic.description) {
40409
- const indentWidth = 1 + 1 + (MAX_SEVERITY_LENGTH + 2) + 1;
40410
- const indent = " ".repeat(indentWidth);
40411
- const availableWidth = columns - indentWidth;
40412
- const truncatedDescription = availableWidth > 0 && diagnostic.description.length > availableWidth ? diagnostic.description.substring(0, availableWidth - 1) + "\u2026" : diagnostic.description;
40413
- const descriptionStr = ansi(indent + truncatedDescription, DIM);
40414
- documents.push(mainLine + "\n" + descriptionStr);
40415
- } else {
40416
- documents.push(mainLine);
40417
- }
40418
- }
40419
- return documents.join("\n");
40420
- }
40421
- function renderNextFrame(state, options) {
40422
- return gen2(function* () {
40423
- const terminal = yield* Terminal;
40424
- const columns = yield* terminal.columns;
40425
- const figs = figuresValue;
40426
- const diagnosticsStr = renderDiagnostics(state, options, figs, columns);
40427
- const leadingSymbol = ansi("?", CYAN_BRIGHT);
40428
- const trailingSymbol = figs.pointerSmall;
40429
- const promptMsg = renderOutput(leadingSymbol, trailingSymbol, options);
40430
- const helpText = ansi(
40431
- "Use \u2191/\u2193 to navigate, \u2190/\u2192 to change severity, Enter to finish",
40432
- DIM
40433
- );
40434
- return CURSOR_HIDE + promptMsg + "\n" + helpText + "\n" + diagnosticsStr;
40435
- });
40436
- }
40437
- function renderSubmission(state, options) {
40438
- return gen2(function* () {
40439
- const figs = figuresValue;
40440
- const changedCount = Object.entries(state.severities).filter(([name, severity]) => {
40441
- const diagnostic = options.diagnostics.find((d) => d.name === name);
40442
- return diagnostic && severity !== diagnostic.defaultSeverity;
40443
- }).length;
40444
- const result3 = ansi(
40445
- `${changedCount} diagnostic${changedCount === 1 ? "" : "s"} configured`,
40446
- WHITE
40447
- );
40448
- const leadingSymbol = ansi(figs.tick, GREEN);
40449
- const trailingSymbol = "";
40450
- const promptMsg = renderOutput(leadingSymbol, trailingSymbol, options);
40451
- return promptMsg + " " + result3 + "\n";
40452
- });
40453
- }
40454
- function processCursorUp(state, totalCount) {
40455
- const newIndex = state.index === 0 ? totalCount - 1 : state.index - 1;
40456
- return succeed6(Action2.NextFrame({ state: { ...state, index: newIndex } }));
40457
- }
40458
- function processCursorDown(state, totalCount) {
40459
- const newIndex = (state.index + 1) % totalCount;
40460
- return succeed6(Action2.NextFrame({ state: { ...state, index: newIndex } }));
40461
- }
40462
- function processSeverityChange(state, options, direction) {
40463
- const diagnostic = options.diagnostics[state.index];
40464
- const currentSeverity = state.severities[diagnostic.name] ?? diagnostic.defaultSeverity;
40465
- const newSeverity = cycleSeverity(currentSeverity, direction);
40466
- return succeed6(Action2.NextFrame({
40467
- state: {
40468
- ...state,
40469
- severities: { ...state.severities, [diagnostic.name]: newSeverity }
40633
+ // src/metadata.json
40634
+ var metadata_default = {
40635
+ groups: [
40636
+ {
40637
+ id: "correctness",
40638
+ name: "Correctness",
40639
+ description: "Wrong, unsafe, or structurally invalid code patterns."
40640
+ },
40641
+ {
40642
+ id: "antipattern",
40643
+ name: "Anti-pattern",
40644
+ description: "Discouraged patterns that often lead to bugs or confusing behavior."
40645
+ },
40646
+ {
40647
+ id: "effectNative",
40648
+ name: "Effect-native",
40649
+ description: "Prefer Effect-native APIs and abstractions when available."
40650
+ },
40651
+ {
40652
+ id: "style",
40653
+ name: "Style",
40654
+ description: "Cleanup, consistency, and idiomatic Effect code."
40470
40655
  }
40471
- }));
40472
- }
40473
- function handleProcess(options) {
40474
- return (input, state) => {
40475
- const totalCount = options.diagnostics.length;
40476
- switch (input.key.name) {
40477
- case "k":
40478
- case "up": {
40479
- return processCursorUp(state, totalCount);
40656
+ ],
40657
+ rules: [
40658
+ {
40659
+ name: "anyUnknownInErrorContext",
40660
+ group: "correctness",
40661
+ description: "Detects 'any' or 'unknown' types in Effect error or requirements channels",
40662
+ defaultSeverity: "off",
40663
+ fixable: false,
40664
+ supportedEffect: [
40665
+ "v3",
40666
+ "v4"
40667
+ ],
40668
+ preview: {
40669
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.gen(function*() {\n yield* Effect.services<unknown>()\n return yield* Effect.fail<any>("boom")\n})\n',
40670
+ diagnostics: [
40671
+ {
40672
+ start: 46,
40673
+ end: 53,
40674
+ text: "This has unknown in the requirements channel and any in the error channel which is not recommended.\nOnly service identifiers should appear in the requirements channel.\nHaving an unknown or any error type is not useful. Consider instead using specific error types baked by Data.TaggedError for example. effect(anyUnknownInErrorContext)"
40675
+ },
40676
+ {
40677
+ start: 90,
40678
+ end: 116,
40679
+ text: "This has unknown in the requirements channel which is not recommended.\nOnly service identifiers should appear in the requirements channel. effect(anyUnknownInErrorContext)"
40680
+ },
40681
+ {
40682
+ start: 133,
40683
+ end: 157,
40684
+ text: "This has any in the error channel which is not recommended.\nHaving an unknown or any error type is not useful. Consider instead using specific error types baked by Data.TaggedError for example. effect(anyUnknownInErrorContext)"
40685
+ }
40686
+ ]
40480
40687
  }
40481
- case "j":
40482
- case "down": {
40483
- return processCursorDown(state, totalCount);
40688
+ },
40689
+ {
40690
+ name: "classSelfMismatch",
40691
+ group: "correctness",
40692
+ description: "Ensures Self type parameter matches the class name in Service/Tag/Schema classes",
40693
+ defaultSeverity: "error",
40694
+ fixable: true,
40695
+ supportedEffect: [
40696
+ "v3",
40697
+ "v4"
40698
+ ],
40699
+ preview: {
40700
+ sourceText: 'import * as Effect from "effect/Effect"\n\ninterface ServiceShape { value: number }\n\nexport class InvalidContextTag\n extends Effect.Tag("ValidContextTag")<ValidContextTag, ServiceShape>() {}\n\ndeclare class ValidContextTag {}\n',
40701
+ diagnostics: [
40702
+ {
40703
+ start: 154,
40704
+ end: 169,
40705
+ text: "Self type parameter should be 'InvalidContextTag' effect(classSelfMismatch)"
40706
+ }
40707
+ ]
40484
40708
  }
40485
- case "left": {
40486
- return processSeverityChange(state, options, "left");
40709
+ },
40710
+ {
40711
+ name: "duplicatePackage",
40712
+ group: "correctness",
40713
+ description: "Detects when multiple versions of the same Effect package are loaded",
40714
+ defaultSeverity: "warning",
40715
+ fixable: false,
40716
+ supportedEffect: [
40717
+ "v3",
40718
+ "v4"
40719
+ ],
40720
+ preview: {
40721
+ sourceText: 'import * as Effect from "effect/Effect"\n\n// This preview only reports when the workspace resolves duplicated\n// Effect package versions.\nexport const preview = Effect.succeed(true)\n',
40722
+ diagnostics: []
40487
40723
  }
40488
- case "right": {
40489
- return processSeverityChange(state, options, "right");
40724
+ },
40725
+ {
40726
+ name: "floatingEffect",
40727
+ group: "correctness",
40728
+ description: "Ensures Effects are yielded or assigned to variables, not left floating",
40729
+ defaultSeverity: "error",
40730
+ fixable: false,
40731
+ supportedEffect: [
40732
+ "v3",
40733
+ "v4"
40734
+ ],
40735
+ preview: {
40736
+ sourceText: 'import * as Effect from "effect/Effect"\n\nEffect.log("forgotten")\n',
40737
+ diagnostics: [
40738
+ {
40739
+ start: 41,
40740
+ end: 64,
40741
+ text: "Effect must be yielded or assigned to a variable. effect(floatingEffect)"
40742
+ }
40743
+ ]
40490
40744
  }
40491
- case "enter":
40492
- case "return": {
40493
- return succeed6(Action2.Submit({ value: state.severities }));
40745
+ },
40746
+ {
40747
+ name: "genericEffectServices",
40748
+ group: "correctness",
40749
+ description: "Prevents services with type parameters that cannot be discriminated at runtime",
40750
+ defaultSeverity: "warning",
40751
+ fixable: false,
40752
+ supportedEffect: [
40753
+ "v3",
40754
+ "v4"
40755
+ ],
40756
+ preview: {
40757
+ sourceText: 'import { Effect } from "effect"\n\nexport class Preview<_A> extends Effect.Service<Preview<any>>()("Preview", {\n succeed: {}\n}) {}\n',
40758
+ diagnostics: [
40759
+ {
40760
+ start: 46,
40761
+ end: 53,
40762
+ text: "Effect Services with type parameters are not supported because they cannot be properly discriminated at runtime, which may cause unexpected behavior. effect(genericEffectServices)"
40763
+ }
40764
+ ]
40494
40765
  }
40495
- default: {
40496
- return succeed6(Action2.Beep());
40766
+ },
40767
+ {
40768
+ name: "missingEffectContext",
40769
+ group: "correctness",
40770
+ description: "Reports missing service requirements in Effect context channel",
40771
+ defaultSeverity: "error",
40772
+ fixable: false,
40773
+ supportedEffect: [
40774
+ "v3",
40775
+ "v4"
40776
+ ],
40777
+ preview: {
40778
+ sourceText: 'import { Effect, ServiceMap } from "effect"\n\nclass Db extends ServiceMap.Service<Db>()("Db", { make: Effect.succeed({}) }) {}\n\n// @ts-expect-error\nexport const preview: Effect.Effect<void> = Db.asEffect().pipe(Effect.asVoid)\n',
40779
+ diagnostics: [
40780
+ {
40781
+ start: 160,
40782
+ end: 167,
40783
+ text: "Missing 'Db' in the expected Effect context. effect(missingEffectContext)"
40784
+ }
40785
+ ]
40497
40786
  }
40498
- }
40499
- };
40500
- }
40501
- function handleClear(options) {
40502
- return gen2(function* () {
40503
- const terminal = yield* Terminal;
40504
- const columns = yield* terminal.columns;
40505
- const clearPrompt = ERASE_LINE + CURSOR_LEFT;
40506
- const visibleCount = Math.min(options.diagnostics.length, options.maxPerPage);
40507
- const text2 = "\n".repeat(visibleCount + 2) + options.message;
40508
- const clearOutput = eraseText2(text2, columns);
40509
- return clearOutput + clearPrompt;
40510
- });
40511
- }
40512
- function handleRender(options) {
40513
- return (state, action2) => {
40514
- return Action2.$match(action2, {
40515
- Beep: () => succeed6(BEEP),
40516
- NextFrame: ({ state: state2 }) => renderNextFrame(state2, options),
40517
- Submit: () => renderSubmission(state, options)
40518
- });
40519
- };
40520
- }
40521
- function createDiagnosticPrompt(diagnostics3, initialSeverities) {
40522
- const options = {
40523
- message: "Configure Diagnostic Severities",
40524
- diagnostics: diagnostics3,
40525
- maxPerPage: 10
40526
- };
40527
- const initialState = {
40528
- index: 0,
40529
- severities: initialSeverities
40530
- };
40531
- return custom(initialState, {
40532
- render: handleRender(options),
40533
- process: handleProcess(options),
40534
- clear: () => handleClear(options)
40787
+ },
40788
+ {
40789
+ name: "missingEffectError",
40790
+ group: "correctness",
40791
+ description: "Reports missing error types in Effect error channel",
40792
+ defaultSeverity: "error",
40793
+ fixable: true,
40794
+ supportedEffect: [
40795
+ "v3",
40796
+ "v4"
40797
+ ],
40798
+ preview: {
40799
+ sourceText: 'import type * as Effect from "effect/Effect"\nimport { Data } from "effect"\n\nclass Boom extends Data.TaggedError("Boom")<{}> {}\ndeclare const effect: Effect.Effect<number, Boom>\n\n// @ts-expect-error\nexport const preview: () => Effect.Effect<number> = () => effect\n',
40800
+ diagnostics: [
40801
+ {
40802
+ start: 256,
40803
+ end: 262,
40804
+ text: "Missing 'Boom' in the expected Effect errors. effect(missingEffectError)"
40805
+ }
40806
+ ]
40807
+ }
40808
+ },
40809
+ {
40810
+ name: "missingLayerContext",
40811
+ group: "correctness",
40812
+ description: "Reports missing service requirements in Layer context channel",
40813
+ defaultSeverity: "error",
40814
+ fixable: false,
40815
+ supportedEffect: [
40816
+ "v3",
40817
+ "v4"
40818
+ ],
40819
+ preview: {
40820
+ sourceText: 'import { Effect, Layer, ServiceMap } from "effect"\n\nclass A extends ServiceMap.Service<A>()("A", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\ndeclare const layer: Layer.Layer<A, never, A>\n// @ts-expect-error\nexport const preview: Layer.Layer<A> = layer\n',
40821
+ diagnostics: [
40822
+ {
40823
+ start: 259,
40824
+ end: 266,
40825
+ text: "Missing 'A' in the expected Layer context. effect(missingLayerContext)"
40826
+ }
40827
+ ]
40828
+ }
40829
+ },
40830
+ {
40831
+ name: "missingReturnYieldStar",
40832
+ group: "correctness",
40833
+ description: "Suggests using 'return yield*' for Effects with never success for better type narrowing",
40834
+ defaultSeverity: "error",
40835
+ fixable: true,
40836
+ supportedEffect: [
40837
+ "v3",
40838
+ "v4"
40839
+ ],
40840
+ preview: {
40841
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n yield* Effect.log("before")\n yield* Effect.fail("boom")\n})\n',
40842
+ diagnostics: [
40843
+ {
40844
+ start: 121,
40845
+ end: 147,
40846
+ text: "It is recommended to use return yield* for Effects that never succeed to signal a definitive exit point for type narrowing and tooling support. effect(missingReturnYieldStar)"
40847
+ }
40848
+ ]
40849
+ }
40850
+ },
40851
+ {
40852
+ name: "missingStarInYieldEffectGen",
40853
+ group: "correctness",
40854
+ description: "Enforces using 'yield*' instead of 'yield' when yielding Effects in generators",
40855
+ defaultSeverity: "error",
40856
+ fixable: true,
40857
+ supportedEffect: [
40858
+ "v3",
40859
+ "v4"
40860
+ ],
40861
+ preview: {
40862
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n const value = yield Effect.succeed(1)\n return value\n})\n',
40863
+ diagnostics: [
40864
+ {
40865
+ start: 75,
40866
+ end: 83,
40867
+ text: "Seems like you used yield instead of yield* inside this Effect.gen. effect(missingStarInYieldEffectGen)"
40868
+ },
40869
+ {
40870
+ start: 105,
40871
+ end: 128,
40872
+ text: "When yielding Effects inside Effect.gen, you should use yield* instead of yield. effect(missingStarInYieldEffectGen)"
40873
+ }
40874
+ ]
40875
+ }
40876
+ },
40877
+ {
40878
+ name: "nonObjectEffectServiceType",
40879
+ group: "correctness",
40880
+ description: "Ensures Effect.Service types are objects, not primitives",
40881
+ defaultSeverity: "error",
40882
+ fixable: false,
40883
+ supportedEffect: [
40884
+ "v3"
40885
+ ],
40886
+ preview: {
40887
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport class BadService extends Effect.Service<BadService>()("BadService", {\n // @ts-expect-error\n succeed: "hello" as const\n}) {}\n',
40888
+ diagnostics: [
40889
+ {
40890
+ start: 142,
40891
+ end: 149,
40892
+ text: "Effect.Service requires the service type to be an object {} and not a primitive type. \nConsider wrapping the value in an object, or manually using Context.Tag or Effect.Tag if you want to use a primitive instead. effect(nonObjectEffectServiceType)"
40893
+ }
40894
+ ]
40895
+ }
40896
+ },
40897
+ {
40898
+ name: "outdatedApi",
40899
+ group: "correctness",
40900
+ description: "Detects usage of APIs that have been removed or renamed in Effect v4",
40901
+ defaultSeverity: "warning",
40902
+ fixable: false,
40903
+ supportedEffect: [
40904
+ "v4"
40905
+ ],
40906
+ preview: {
40907
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.gen(function*() {\n // @ts-expect-error\n return yield* Effect.runtime()\n})\n',
40908
+ diagnostics: [
40909
+ {
40910
+ start: 0,
40911
+ end: 0,
40912
+ text: "This project targets Effect v4, but is using Effect v3 APIs. To find the correct API to use, clone and consult the github.com/effect-ts/effect-smol repository for the corresponding v4 replacement. effect(outdatedApi)"
40913
+ },
40914
+ {
40915
+ start: 126,
40916
+ end: 133,
40917
+ text: "runtime is an Effect v3 API, but the project is using Effect v4. effect(outdatedApi)"
40918
+ }
40919
+ ]
40920
+ }
40921
+ },
40922
+ {
40923
+ name: "outdatedEffectCodegen",
40924
+ group: "correctness",
40925
+ description: "Detects when generated code is outdated and needs to be regenerated",
40926
+ defaultSeverity: "warning",
40927
+ fixable: true,
40928
+ supportedEffect: [
40929
+ "v3",
40930
+ "v4"
40931
+ ],
40932
+ preview: {
40933
+ sourceText: 'import * as Effect from "effect/Effect"\n\n// @effect-codegens accessors:stale-preview\nexport class Preview extends Effect.Service<Preview>()("Preview", {\n accessors: true,\n effect: Effect.succeed({ value: Effect.succeed(1) })\n}) {}\n',
40934
+ diagnostics: [
40935
+ {
40936
+ start: 61,
40937
+ end: 76,
40938
+ text: "Codegen accessors result is outdated effect(outdatedEffectCodegen)"
40939
+ }
40940
+ ]
40941
+ }
40942
+ },
40943
+ {
40944
+ name: "overriddenSchemaConstructor",
40945
+ group: "correctness",
40946
+ description: "Prevents overriding constructors in Schema classes which breaks decoding behavior",
40947
+ defaultSeverity: "error",
40948
+ fixable: true,
40949
+ supportedEffect: [
40950
+ "v3",
40951
+ "v4"
40952
+ ],
40953
+ preview: {
40954
+ sourceText: 'import * as Schema from "effect/Schema"\n\nexport class User extends Schema.Class<User>("User")({ name: Schema.String }) {\n constructor(readonly input: { name: string }) { super(input) }\n}\n',
40955
+ diagnostics: [
40956
+ {
40957
+ start: 123,
40958
+ end: 185,
40959
+ text: "Classes extending Schema must not override the constructor; this is because it silently breaks the schema decoding behaviour. If that's needed, we recommend instead to use a static 'new' method that constructs the instance. effect(overriddenSchemaConstructor)"
40960
+ }
40961
+ ]
40962
+ }
40963
+ },
40964
+ {
40965
+ name: "unsupportedServiceAccessors",
40966
+ group: "correctness",
40967
+ description: "Warns about service accessors that need codegen due to generic/complex signatures",
40968
+ defaultSeverity: "warning",
40969
+ fixable: true,
40970
+ supportedEffect: [
40971
+ "v3",
40972
+ "v4"
40973
+ ],
40974
+ preview: {
40975
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport class Preview extends Effect.Service<Preview>()("Preview", {\n accessors: true,\n effect: Effect.succeed({ get: <A>(value: A) => Effect.succeed(value) })\n}) {}\n',
40976
+ diagnostics: [
40977
+ {
40978
+ start: 54,
40979
+ end: 61,
40980
+ text: "Even if accessors are enabled, accessors for 'get' won't be available because the signature have generic type parameters or multiple call signatures. effect(unsupportedServiceAccessors)"
40981
+ }
40982
+ ]
40983
+ }
40984
+ },
40985
+ {
40986
+ name: "catchUnfailableEffect",
40987
+ group: "antipattern",
40988
+ description: "Warns when using error handling on Effects that never fail (error type is 'never')",
40989
+ defaultSeverity: "suggestion",
40990
+ fixable: false,
40991
+ supportedEffect: [
40992
+ "v3",
40993
+ "v4"
40994
+ ],
40995
+ preview: {
40996
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.succeed(1).pipe(\n Effect.catch(() => Effect.succeed(0))\n)\n',
40997
+ diagnostics: [
40998
+ {
40999
+ start: 82,
41000
+ end: 94,
41001
+ text: "Looks like the previous effect never fails, so probably this error handling will never be triggered. effect(catchUnfailableEffect)"
41002
+ }
41003
+ ]
41004
+ }
41005
+ },
41006
+ {
41007
+ name: "effectFnIife",
41008
+ group: "antipattern",
41009
+ description: "Effect.fn or Effect.fnUntraced is called as an IIFE (Immediately Invoked Function Expression). Use Effect.gen instead.",
41010
+ defaultSeverity: "warning",
41011
+ fixable: true,
41012
+ supportedEffect: [
41013
+ "v3",
41014
+ "v4"
41015
+ ],
41016
+ preview: {
41017
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.fn("preview")(function*() {\n return yield* Effect.succeed(1)\n})()\n',
41018
+ diagnostics: [
41019
+ {
41020
+ start: 64,
41021
+ end: 137,
41022
+ text: `Effect.fn returns a reusable function that can take arguments, but here it's called immediately. Use Effect.gen instead with Effect.withSpan("preview") piped in the end to mantain tracing spans. effect(effectFnIife)`
41023
+ }
41024
+ ]
41025
+ }
41026
+ },
41027
+ {
41028
+ name: "effectGenUsesAdapter",
41029
+ group: "antipattern",
41030
+ description: "Warns when using the deprecated adapter parameter in Effect.gen",
41031
+ defaultSeverity: "warning",
41032
+ fixable: false,
41033
+ supportedEffect: [
41034
+ "v3",
41035
+ "v4"
41036
+ ],
41037
+ preview: {
41038
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*(_) {\n return yield* Effect.succeed(1)\n})\n',
41039
+ diagnostics: [
41040
+ {
41041
+ start: 85,
41042
+ end: 86,
41043
+ text: "The adapter of Effect.gen is not required anymore, it is now just an alias of pipe. effect(effectGenUsesAdapter)"
41044
+ }
41045
+ ]
41046
+ }
41047
+ },
41048
+ {
41049
+ name: "effectInFailure",
41050
+ group: "antipattern",
41051
+ description: "Warns when an Effect is used inside an Effect failure channel",
41052
+ defaultSeverity: "warning",
41053
+ fixable: false,
41054
+ supportedEffect: [
41055
+ "v3",
41056
+ "v4"
41057
+ ],
41058
+ preview: {
41059
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.try({\n try: () => JSON.parse("{"),\n catch: (error) => Effect.logError(error)\n})\n',
41060
+ diagnostics: [
41061
+ {
41062
+ start: 46,
41063
+ end: 53,
41064
+ text: "The error channel contains an Effect (Effect<void, never, never>). Putting Effect computations in the failure channel is not intended; keep only failure types there. effect(effectInFailure)"
41065
+ },
41066
+ {
41067
+ start: 56,
41068
+ end: 144,
41069
+ text: "The error channel contains an Effect (Effect<void, never, never>). Putting Effect computations in the failure channel is not intended; keep only failure types there. effect(effectInFailure)"
41070
+ }
41071
+ ]
41072
+ }
41073
+ },
41074
+ {
41075
+ name: "effectInVoidSuccess",
41076
+ group: "antipattern",
41077
+ description: "Detects nested Effects in void success channels that may cause unexecuted effects",
41078
+ defaultSeverity: "warning",
41079
+ fixable: false,
41080
+ supportedEffect: [
41081
+ "v3",
41082
+ "v4"
41083
+ ],
41084
+ preview: {
41085
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview: Effect.Effect<void> = Effect.succeed(\n Effect.log("nested")\n)\n',
41086
+ diagnostics: [
41087
+ {
41088
+ start: 54,
41089
+ end: 61,
41090
+ text: "There is a nested 'Effect<void, never, never>' in the 'void' success channel, beware that this could lead to nested Effect<Effect<...>> that won't be executed. effect(effectInVoidSuccess)"
41091
+ }
41092
+ ]
41093
+ }
41094
+ },
41095
+ {
41096
+ name: "globalErrorInEffectCatch",
41097
+ group: "antipattern",
41098
+ description: "Warns when catch callbacks return global Error type instead of typed errors",
41099
+ defaultSeverity: "warning",
41100
+ fixable: false,
41101
+ supportedEffect: [
41102
+ "v3",
41103
+ "v4"
41104
+ ],
41105
+ preview: {
41106
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.tryPromise({\n try: async () => 1,\n catch: (error) => new Error(String(error))\n})\n',
41107
+ diagnostics: [
41108
+ {
41109
+ start: 56,
41110
+ end: 73,
41111
+ text: "The 'catch' callback in Effect.tryPromise returns global 'Error', which loses type safety as untagged errors merge together. Consider using a tagged error and optionally wrapping the original in a 'cause' property. effect(globalErrorInEffectCatch)"
41112
+ }
41113
+ ]
41114
+ }
41115
+ },
41116
+ {
41117
+ name: "globalErrorInEffectFailure",
41118
+ group: "antipattern",
41119
+ description: "Warns when the global Error type is used in an Effect failure channel",
41120
+ defaultSeverity: "warning",
41121
+ fixable: false,
41122
+ supportedEffect: [
41123
+ "v3",
41124
+ "v4"
41125
+ ],
41126
+ preview: {
41127
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.fail(new Error("boom"))\n',
41128
+ diagnostics: [
41129
+ {
41130
+ start: 68,
41131
+ end: 85,
41132
+ text: "Global 'Error' loses type safety as untagged errors merge together in the Effect failure channel. Consider using a tagged error and optionally wrapping the original in a 'cause' property. effect(globalErrorInEffectFailure)"
41133
+ }
41134
+ ]
41135
+ }
41136
+ },
41137
+ {
41138
+ name: "layerMergeAllWithDependencies",
41139
+ group: "antipattern",
41140
+ description: "Detects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires",
41141
+ defaultSeverity: "warning",
41142
+ fixable: true,
41143
+ supportedEffect: [
41144
+ "v3",
41145
+ "v4"
41146
+ ],
41147
+ preview: {
41148
+ sourceText: 'import { Effect, Layer, ServiceMap } from "effect"\n\nclass A extends ServiceMap.Service<A>()("A", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\nclass B extends ServiceMap.Service<B>()("B", { make: Effect.as(A.asEffect(), {}) }) {\n static Default = Layer.effect(this, this.make)\n}\nexport const preview = Layer.mergeAll(A.Default, B.Default)\n',
41149
+ diagnostics: [
41150
+ {
41151
+ start: 355,
41152
+ end: 364,
41153
+ text: "This layer provides A which is required by another layer in the same Layer.mergeAll call. Layer.mergeAll creates layers in parallel, so dependencies between layers will not be satisfied. Consider moving this layer into a Layer.provideMerge after the Layer.mergeAll. effect(layerMergeAllWithDependencies)"
41154
+ }
41155
+ ]
41156
+ }
41157
+ },
41158
+ {
41159
+ name: "leakingRequirements",
41160
+ group: "antipattern",
41161
+ description: "Detects implementation services leaked in service methods",
41162
+ defaultSeverity: "suggestion",
41163
+ fixable: false,
41164
+ supportedEffect: [
41165
+ "v3",
41166
+ "v4"
41167
+ ],
41168
+ preview: {
41169
+ sourceText: 'import type { Effect } from "effect"\nimport { ServiceMap } from "effect"\n\nclass FileSystem extends ServiceMap.Service<FileSystem, {\n write: (s: string) => Effect.Effect<void>\n}>()("FileSystem") {}\n\nexport class Cache extends ServiceMap.Service<Cache, {\n read: Effect.Effect<string, never, FileSystem>\n save: () => Effect.Effect<void, never, FileSystem>\n}>()("Cache") {}\n',
41170
+ diagnostics: [
41171
+ {
41172
+ start: 212,
41173
+ end: 217,
41174
+ text: "Methods of this Service require `FileSystem` from every caller.\n\nThis leaks implementation details into the service's public type \u2014 callers shouldn't need to know *how* the service works internally, only *what* it provides.\n\nResolve these dependencies at Layer creation and provide them to each method, so the service's type reflects its purpose, not its implementation.\n\nTo suppress this diagnostic for specific dependency types that are intentionally passed through (e.g., HttpServerRequest), add `@effect-leakable-service` JSDoc to their interface declarations (e.g., the `FileSystem` interface), not to this service.\n\nMore info and examples at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage effect(leakingRequirements)"
41175
+ }
41176
+ ]
41177
+ }
41178
+ },
41179
+ {
41180
+ name: "multipleEffectProvide",
41181
+ group: "antipattern",
41182
+ description: "Warns against chaining Effect.provide calls which can cause service lifecycle issues",
41183
+ defaultSeverity: "warning",
41184
+ fixable: true,
41185
+ supportedEffect: [
41186
+ "v3",
41187
+ "v4"
41188
+ ],
41189
+ preview: {
41190
+ sourceText: 'import { Effect, Layer, ServiceMap } from "effect"\n\nclass A extends ServiceMap.Service<A>()("A", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\nclass B extends ServiceMap.Service<B>()("B", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\nexport const preview = Effect.void.pipe(Effect.provide(A.Default), Effect.provide(B.Default))\n',
41191
+ diagnostics: [
41192
+ {
41193
+ start: 348,
41194
+ end: 373,
41195
+ text: "Avoid chaining Effect.provide calls, as this can lead to service lifecycle issues. Instead, merge layers and provide them in a single call. effect(multipleEffectProvide)"
41196
+ }
41197
+ ]
41198
+ }
41199
+ },
41200
+ {
41201
+ name: "returnEffectInGen",
41202
+ group: "antipattern",
41203
+ description: "Warns when returning an Effect in a generator causes nested Effect<Effect<...>>",
41204
+ defaultSeverity: "suggestion",
41205
+ fixable: true,
41206
+ supportedEffect: [
41207
+ "v3",
41208
+ "v4"
41209
+ ],
41210
+ preview: {
41211
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n return Effect.succeed(1)\n})\n',
41212
+ diagnostics: [
41213
+ {
41214
+ start: 91,
41215
+ end: 115,
41216
+ text: "You are returning an Effect-able type inside a generator function, and will result in nested Effect<Effect<...>>.\nMaybe you wanted to return yield* instead?\nNested Effect-able types may be intended if you plan to later manually flatten or unwrap this Effect, if so you can safely disable this diagnostic for this line through quickfixes. effect(returnEffectInGen)"
41217
+ }
41218
+ ]
41219
+ }
41220
+ },
41221
+ {
41222
+ name: "runEffectInsideEffect",
41223
+ group: "antipattern",
41224
+ description: "Suggests using Runtime methods instead of Effect.run* inside Effect contexts",
41225
+ defaultSeverity: "suggestion",
41226
+ fixable: true,
41227
+ supportedEffect: [
41228
+ "v3"
41229
+ ],
41230
+ preview: {
41231
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.gen(function*() {\n const run = () => Effect.runSync(Effect.succeed(1))\n return run()\n})\n',
41232
+ diagnostics: [
41233
+ {
41234
+ start: 101,
41235
+ end: 115,
41236
+ text: "Using Effect.runSync inside an Effect is not recommended. The same runtime should generally be used instead to run child effects.\nConsider extracting the Runtime by using for example Effect.runtime and then use Runtime.runSync with the extracted runtime instead. effect(runEffectInsideEffect)"
41237
+ }
41238
+ ]
41239
+ }
41240
+ },
41241
+ {
41242
+ name: "schemaSyncInEffect",
41243
+ group: "antipattern",
41244
+ description: "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
41245
+ defaultSeverity: "suggestion",
41246
+ fixable: false,
41247
+ supportedEffect: [
41248
+ "v3"
41249
+ ],
41250
+ preview: {
41251
+ sourceText: 'import * as Effect from "effect/Effect"\nimport * as Schema from "effect/Schema"\n\nconst Person = Schema.Struct({ name: Schema.String, age: Schema.Number })\n\nexport const preview = Effect.gen(function*() {\n const input = yield* Effect.succeed({ name: "Ada", age: 1 })\n return Schema.decodeSync(Person)(input)\n})\n',
41252
+ diagnostics: [
41253
+ {
41254
+ start: 276,
41255
+ end: 293,
41256
+ text: "Using Schema.decodeSync inside an Effect generator is not recommended. Use Schema.decode instead to get properly typed error channel. effect(schemaSyncInEffect)"
41257
+ }
41258
+ ]
41259
+ }
41260
+ },
41261
+ {
41262
+ name: "scopeInLayerEffect",
41263
+ group: "antipattern",
41264
+ description: "Suggests using Layer.scoped instead of Layer.effect when Scope is in requirements",
41265
+ defaultSeverity: "warning",
41266
+ fixable: true,
41267
+ supportedEffect: [
41268
+ "v3"
41269
+ ],
41270
+ preview: {
41271
+ sourceText: 'import * as Context from "effect/Context"\nimport * as Effect from "effect/Effect"\nimport * as Layer from "effect/Layer"\n\nclass Cache extends Context.Tag("Cache")<Cache, { ok: true }>() {}\nexport const preview = Layer.effect(Cache, Effect.gen(function*() {\n yield* Effect.addFinalizer(() => Effect.void)\n return { ok: true as const }\n}))\n',
41272
+ diagnostics: [
41273
+ {
41274
+ start: 211,
41275
+ end: 338,
41276
+ text: 'Seems like you are constructing a layer with a scope in the requirements.\nConsider using "scoped" instead to get rid of the scope in the requirements. effect(scopeInLayerEffect)'
41277
+ }
41278
+ ]
41279
+ }
41280
+ },
41281
+ {
41282
+ name: "strictEffectProvide",
41283
+ group: "antipattern",
41284
+ description: "Warns when using Effect.provide with layers outside of application entry points",
41285
+ defaultSeverity: "off",
41286
+ fixable: false,
41287
+ supportedEffect: [
41288
+ "v3",
41289
+ "v4"
41290
+ ],
41291
+ preview: {
41292
+ sourceText: 'import { Effect, Layer, ServiceMap } from "effect"\n\nclass Config extends ServiceMap.Service<Config>()("Config", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\nexport const preview = Effect.void.pipe(Effect.provide(Config.Default))\n',
41293
+ diagnostics: [
41294
+ {
41295
+ start: 235,
41296
+ end: 265,
41297
+ text: "Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide)"
41298
+ }
41299
+ ]
41300
+ }
41301
+ },
41302
+ {
41303
+ name: "tryCatchInEffectGen",
41304
+ group: "antipattern",
41305
+ description: "Discourages try/catch in Effect generators in favor of Effect error handling",
41306
+ defaultSeverity: "suggestion",
41307
+ fixable: false,
41308
+ supportedEffect: [
41309
+ "v3",
41310
+ "v4"
41311
+ ],
41312
+ preview: {
41313
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n try { return yield* Effect.succeed(1) }\n catch { return 0 }\n})\n',
41314
+ diagnostics: [
41315
+ {
41316
+ start: 91,
41317
+ end: 151,
41318
+ text: "Avoid using try/catch inside Effect generators. Use Effect's error handling mechanisms instead (e.g. Effect.try, Effect.tryPromise, Effect.catch, Effect.catchTag). effect(tryCatchInEffectGen)"
41319
+ }
41320
+ ]
41321
+ }
41322
+ },
41323
+ {
41324
+ name: "unknownInEffectCatch",
41325
+ group: "antipattern",
41326
+ description: "Warns when catch callbacks return unknown instead of typed errors",
41327
+ defaultSeverity: "warning",
41328
+ fixable: false,
41329
+ supportedEffect: [
41330
+ "v3",
41331
+ "v4"
41332
+ ],
41333
+ preview: {
41334
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.tryPromise({\n try: async () => 1,\n catch: (error) => error\n})\n',
41335
+ diagnostics: [
41336
+ {
41337
+ start: 56,
41338
+ end: 73,
41339
+ text: "The 'catch' callback in Effect.tryPromise returns 'unknown'. The catch callback should be used to provide typed errors.\nConsider wrapping unknown errors into Effect's Data.TaggedError for example, or narrow down the type to the specific error raised. effect(unknownInEffectCatch)"
41340
+ }
41341
+ ]
41342
+ }
41343
+ },
41344
+ {
41345
+ name: "extendsNativeError",
41346
+ group: "effectNative",
41347
+ description: "Warns when a class directly extends the native Error class",
41348
+ defaultSeverity: "off",
41349
+ fixable: false,
41350
+ supportedEffect: [
41351
+ "v3",
41352
+ "v4"
41353
+ ],
41354
+ preview: {
41355
+ sourceText: "\nexport class PreviewError extends Error {}\n",
41356
+ diagnostics: [
41357
+ {
41358
+ start: 14,
41359
+ end: 26,
41360
+ text: "Avoid extending the native 'Error' class directly. Consider using a tagged error (e.g. Data.TaggedError) to maintain type safety in the Effect failure channel. effect(extendsNativeError)"
41361
+ }
41362
+ ]
41363
+ }
41364
+ },
41365
+ {
41366
+ name: "globalFetch",
41367
+ group: "effectNative",
41368
+ description: "Warns when using the global fetch function instead of the Effect HTTP client",
41369
+ defaultSeverity: "off",
41370
+ fixable: false,
41371
+ supportedEffect: [
41372
+ "v3",
41373
+ "v4"
41374
+ ],
41375
+ preview: {
41376
+ sourceText: '\nexport const preview = fetch("https://example.com")\n',
41377
+ diagnostics: [
41378
+ {
41379
+ start: 24,
41380
+ end: 29,
41381
+ text: "Prefer using HttpClient from @effect/platform instead of the global 'fetch' function. effect(globalFetch)"
41382
+ }
41383
+ ]
41384
+ }
41385
+ },
41386
+ {
41387
+ name: "instanceOfSchema",
41388
+ group: "effectNative",
41389
+ description: "Suggests using Schema.is instead of instanceof for Effect Schema types",
41390
+ defaultSeverity: "off",
41391
+ fixable: true,
41392
+ supportedEffect: [
41393
+ "v3",
41394
+ "v4"
41395
+ ],
41396
+ preview: {
41397
+ sourceText: 'import { Schema } from "effect"\n\nclass User extends Schema.Class<User>("User")({ name: Schema.String }) {}\ndeclare const value: unknown\nexport const preview = value instanceof User\n',
41398
+ diagnostics: [
41399
+ {
41400
+ start: 159,
41401
+ end: 180,
41402
+ text: "Consider using Schema.is instead of instanceof for Effect Schema types. effect(instanceOfSchema)"
41403
+ }
41404
+ ]
41405
+ }
41406
+ },
41407
+ {
41408
+ name: "nodeBuiltinImport",
41409
+ group: "effectNative",
41410
+ description: "Warns when importing Node.js built-in modules that have Effect-native counterparts",
41411
+ defaultSeverity: "off",
41412
+ fixable: false,
41413
+ supportedEffect: [
41414
+ "v3",
41415
+ "v4"
41416
+ ],
41417
+ preview: {
41418
+ sourceText: 'import fs from "node:fs"\n\nexport const preview = fs.readFileSync\n',
41419
+ diagnostics: [
41420
+ {
41421
+ start: 15,
41422
+ end: 24,
41423
+ text: "Prefer using FileSystem from @effect/platform instead of the Node.js 'fs' module. effect(nodeBuiltinImport)"
41424
+ }
41425
+ ]
41426
+ }
41427
+ },
41428
+ {
41429
+ name: "preferSchemaOverJson",
41430
+ group: "effectNative",
41431
+ description: "Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify which may throw",
41432
+ defaultSeverity: "suggestion",
41433
+ fixable: false,
41434
+ supportedEffect: [
41435
+ "v3",
41436
+ "v4"
41437
+ ],
41438
+ preview: {
41439
+ sourceText: `import { Effect } from "effect"
41440
+
41441
+ export const preview = Effect.gen(function*() {
41442
+ const text = yield* Effect.succeed('{"ok":true}')
41443
+ return JSON.parse(text)
41444
+ })
41445
+ `,
41446
+ diagnostics: [
41447
+ {
41448
+ start: 142,
41449
+ end: 158,
41450
+ text: "Consider using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify effect(preferSchemaOverJson)"
41451
+ }
41452
+ ]
41453
+ }
41454
+ },
41455
+ {
41456
+ name: "catchAllToMapError",
41457
+ group: "style",
41458
+ description: "Suggests using Effect.mapError instead of Effect.catchAll when the callback only wraps the error with Effect.fail",
41459
+ defaultSeverity: "suggestion",
41460
+ fixable: true,
41461
+ supportedEffect: [
41462
+ "v3",
41463
+ "v4"
41464
+ ],
41465
+ preview: {
41466
+ sourceText: 'import { Effect } from "effect"\n\nclass WrappedError {\n constructor(readonly cause: unknown) {}\n}\n\nexport const preview = Effect.fail("boom").pipe(\n Effect.catch((cause) => Effect.fail(new WrappedError(cause)))\n)\n',
41467
+ diagnostics: [
41468
+ {
41469
+ start: 150,
41470
+ end: 162,
41471
+ text: "You can use Effect.mapError instead of Effect.catch + Effect.fail to transform the error type. effect(catchAllToMapError)"
41472
+ }
41473
+ ]
41474
+ }
41475
+ },
41476
+ {
41477
+ name: "deterministicKeys",
41478
+ group: "style",
41479
+ description: "Enforces deterministic naming for service/tag/error identifiers based on class names",
41480
+ defaultSeverity: "off",
41481
+ fixable: true,
41482
+ supportedEffect: [
41483
+ "v3",
41484
+ "v4"
41485
+ ],
41486
+ preview: {
41487
+ sourceText: 'import { ServiceMap } from "effect"\n\nexport class RenamedService\n extends ServiceMap.Service<RenamedService, {}>()("CustomIdentifier") {}\n',
41488
+ diagnostics: [
41489
+ {
41490
+ start: 116,
41491
+ end: 134,
41492
+ text: "Key should be '@effect/harness-effect-v4/examples/diagnostics/deterministicKeys_preview/RenamedService' effect(deterministicKeys)"
41493
+ }
41494
+ ]
41495
+ }
41496
+ },
41497
+ {
41498
+ name: "effectFnOpportunity",
41499
+ group: "style",
41500
+ description: "Suggests using Effect.fn for functions that returns an Effect",
41501
+ defaultSeverity: "suggestion",
41502
+ fixable: true,
41503
+ supportedEffect: [
41504
+ "v3",
41505
+ "v4"
41506
+ ],
41507
+ preview: {
41508
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = () => Effect.gen(function*() {\n return yield* Effect.succeed(1)\n})\n',
41509
+ diagnostics: [
41510
+ {
41511
+ start: 54,
41512
+ end: 61,
41513
+ text: "Can be rewritten as a reusable function: Effect.fn(function*() { ... }) effect(effectFnOpportunity)"
41514
+ }
41515
+ ]
41516
+ }
41517
+ },
41518
+ {
41519
+ name: "effectMapVoid",
41520
+ group: "style",
41521
+ description: "Suggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {})",
41522
+ defaultSeverity: "suggestion",
41523
+ fixable: true,
41524
+ supportedEffect: [
41525
+ "v3",
41526
+ "v4"
41527
+ ],
41528
+ preview: {
41529
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.succeed(1).pipe(\n Effect.map(() => undefined)\n)\n',
41530
+ diagnostics: [
41531
+ {
41532
+ start: 82,
41533
+ end: 92,
41534
+ text: "Effect.asVoid can be used instead to discard the success value effect(effectMapVoid)"
41535
+ }
41536
+ ]
41537
+ }
41538
+ },
41539
+ {
41540
+ name: "effectSucceedWithVoid",
41541
+ group: "style",
41542
+ description: "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
41543
+ defaultSeverity: "suggestion",
41544
+ fixable: true,
41545
+ supportedEffect: [
41546
+ "v3",
41547
+ "v4"
41548
+ ],
41549
+ preview: {
41550
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.succeed(undefined)\n',
41551
+ diagnostics: [
41552
+ {
41553
+ start: 56,
41554
+ end: 81,
41555
+ text: "Effect.void can be used instead of Effect.succeed(undefined) or Effect.succeed(void 0) effect(effectSucceedWithVoid)"
41556
+ }
41557
+ ]
41558
+ }
41559
+ },
41560
+ {
41561
+ name: "importFromBarrel",
41562
+ group: "style",
41563
+ description: "Suggests importing from specific module paths instead of barrel exports",
41564
+ defaultSeverity: "off",
41565
+ fixable: true,
41566
+ supportedEffect: [
41567
+ "v3",
41568
+ "v4"
41569
+ ],
41570
+ preview: {
41571
+ sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.void\n',
41572
+ diagnostics: [
41573
+ {
41574
+ start: 9,
41575
+ end: 15,
41576
+ text: "Importing from barrel module effect is not allowed. effect(importFromBarrel)"
41577
+ }
41578
+ ]
41579
+ }
41580
+ },
41581
+ {
41582
+ name: "missedPipeableOpportunity",
41583
+ group: "style",
41584
+ description: "Enforces the use of pipeable style for nested function calls",
41585
+ defaultSeverity: "off",
41586
+ fixable: true,
41587
+ supportedEffect: [
41588
+ "v3",
41589
+ "v4"
41590
+ ],
41591
+ preview: {
41592
+ sourceText: 'import { identity, Schema } from "effect"\n\nconst User = Schema.Struct({ id: Schema.Number })\nexport const preview = identity(identity(Schema.decodeEffect(User)({ id: 1 })))\n',
41593
+ diagnostics: [
41594
+ {
41595
+ start: 116,
41596
+ end: 172,
41597
+ text: "Nested function calls can be converted to pipeable style for better readability; consider using Schema.decodeEffect(User)({ id: 1 }).pipe(...) instead. effect(missedPipeableOpportunity)"
41598
+ }
41599
+ ]
41600
+ }
41601
+ },
41602
+ {
41603
+ name: "missingEffectServiceDependency",
41604
+ group: "style",
41605
+ description: "Checks that Effect.Service dependencies satisfy all required layer inputs",
41606
+ defaultSeverity: "off",
41607
+ fixable: false,
41608
+ supportedEffect: [
41609
+ "v3"
41610
+ ],
41611
+ preview: {
41612
+ sourceText: 'import * as Effect from "effect/Effect"\n\nclass Db extends Effect.Service<Db>()("Db", { succeed: { ok: true } }) {}\nexport class Repo extends Effect.Service<Repo>()("Repo", {\n effect: Effect.gen(function*() {\n yield* Db\n return { all: Effect.succeed([] as Array<number>) }\n })\n}) {}\n',
41613
+ diagnostics: [
41614
+ {
41615
+ start: 128,
41616
+ end: 132,
41617
+ text: "Service 'Db' is required but not provided by dependencies effect(missingEffectServiceDependency)"
41618
+ }
41619
+ ]
41620
+ }
41621
+ },
41622
+ {
41623
+ name: "redundantSchemaTagIdentifier",
41624
+ group: "style",
41625
+ description: "Suggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest",
41626
+ defaultSeverity: "suggestion",
41627
+ fixable: true,
41628
+ supportedEffect: [
41629
+ "v3",
41630
+ "v4"
41631
+ ],
41632
+ preview: {
41633
+ sourceText: 'import * as Schema from "effect/Schema"\n\nexport class Preview\n extends Schema.TaggedClass<Preview>("Preview")("Preview", {\n value: Schema.String\n }) {}\n',
41634
+ diagnostics: [
41635
+ {
41636
+ start: 100,
41637
+ end: 109,
41638
+ text: "Identifier 'Preview' is redundant since it equals the _tag value effect(redundantSchemaTagIdentifier)"
41639
+ }
41640
+ ]
41641
+ }
41642
+ },
41643
+ {
41644
+ name: "schemaStructWithTag",
41645
+ group: "style",
41646
+ description: "Suggests using Schema.TaggedStruct instead of Schema.Struct with _tag field",
41647
+ defaultSeverity: "suggestion",
41648
+ fixable: true,
41649
+ supportedEffect: [
41650
+ "v3",
41651
+ "v4"
41652
+ ],
41653
+ preview: {
41654
+ sourceText: 'import * as Schema from "effect/Schema"\n\nexport const preview = Schema.Struct({\n _tag: Schema.Literal("User"),\n name: Schema.String\n})\n',
41655
+ diagnostics: [
41656
+ {
41657
+ start: 64,
41658
+ end: 136,
41659
+ text: "Schema.Struct with a _tag field can be simplified to Schema.TaggedStruct to make the tag optional in the constructor. effect(schemaStructWithTag)"
41660
+ }
41661
+ ]
41662
+ }
41663
+ },
41664
+ {
41665
+ name: "schemaUnionOfLiterals",
41666
+ group: "style",
41667
+ description: "Simplifies Schema.Union of multiple Schema.Literal calls into single Schema.Literal",
41668
+ defaultSeverity: "off",
41669
+ fixable: true,
41670
+ supportedEffect: [
41671
+ "v3"
41672
+ ],
41673
+ preview: {
41674
+ sourceText: 'import * as Schema from "effect/Schema"\n\nexport const preview = Schema.Union(Schema.Literal("a"), Schema.Literal("b"))\n',
41675
+ diagnostics: [
41676
+ {
41677
+ start: 64,
41678
+ end: 118,
41679
+ text: "A Schema.Union of multiple Schema.Literal calls can be simplified to a single Schema.Literal call. effect(schemaUnionOfLiterals)"
41680
+ }
41681
+ ]
41682
+ }
41683
+ },
41684
+ {
41685
+ name: "serviceNotAsClass",
41686
+ group: "style",
41687
+ description: "Warns when ServiceMap.Service is used as a variable instead of a class declaration",
41688
+ defaultSeverity: "off",
41689
+ fixable: true,
41690
+ supportedEffect: [
41691
+ "v4"
41692
+ ],
41693
+ preview: {
41694
+ sourceText: 'import { ServiceMap } from "effect"\n\nexport const Preview = ServiceMap.Service<{ port: number }>("Preview")\n',
41695
+ diagnostics: [
41696
+ {
41697
+ start: 60,
41698
+ end: 107,
41699
+ text: 'ServiceMap.Service should be used in a class declaration instead of as a variable. Use: class Preview extends ServiceMap.Service<Preview, { port: number }>()("Preview") {} effect(serviceNotAsClass)'
41700
+ }
41701
+ ]
41702
+ }
41703
+ },
41704
+ {
41705
+ name: "strictBooleanExpressions",
41706
+ group: "style",
41707
+ description: "Enforces boolean types in conditional expressions for type safety",
41708
+ defaultSeverity: "off",
41709
+ fixable: false,
41710
+ supportedEffect: [
41711
+ "v3",
41712
+ "v4"
41713
+ ],
41714
+ preview: {
41715
+ sourceText: "\ndeclare const value: string | undefined\nexport const preview = value ? 1 : 0\n",
41716
+ diagnostics: [
41717
+ {
41718
+ start: 64,
41719
+ end: 69,
41720
+ text: "Unexpected `string` type in condition, expected strictly a boolean instead. effect(strictBooleanExpressions)"
41721
+ },
41722
+ {
41723
+ start: 64,
41724
+ end: 69,
41725
+ text: "Unexpected `undefined` type in condition, expected strictly a boolean instead. effect(strictBooleanExpressions)"
41726
+ }
41727
+ ]
41728
+ }
41729
+ },
41730
+ {
41731
+ name: "unnecessaryEffectGen",
41732
+ group: "style",
41733
+ description: "Suggests removing Effect.gen when it contains only a single return statement",
41734
+ defaultSeverity: "suggestion",
41735
+ fixable: true,
41736
+ supportedEffect: [
41737
+ "v3",
41738
+ "v4"
41739
+ ],
41740
+ preview: {
41741
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n return yield* Effect.succeed(1)\n})\n',
41742
+ diagnostics: [
41743
+ {
41744
+ start: 64,
41745
+ end: 125,
41746
+ text: "This Effect.gen contains a single return statement. effect(unnecessaryEffectGen)"
41747
+ }
41748
+ ]
41749
+ }
41750
+ },
41751
+ {
41752
+ name: "unnecessaryFailYieldableError",
41753
+ group: "style",
41754
+ description: "Suggests yielding yieldable errors directly instead of wrapping with Effect.fail",
41755
+ defaultSeverity: "suggestion",
41756
+ fixable: true,
41757
+ supportedEffect: [
41758
+ "v3",
41759
+ "v4"
41760
+ ],
41761
+ preview: {
41762
+ sourceText: 'import * as Data from "effect/Data"\nimport * as Effect from "effect/Effect"\n\nclass Boom extends Data.TaggedError("Boom")<{}> {}\nexport const preview = Effect.gen(function*() {\n yield* Effect.fail(new Boom())\n})\n',
41763
+ diagnostics: [
41764
+ {
41765
+ start: 178,
41766
+ end: 208,
41767
+ text: "This Effect.fail call uses a yieldable error type as argument. You can yield* the error directly instead. effect(unnecessaryFailYieldableError)"
41768
+ }
41769
+ ]
41770
+ }
41771
+ },
41772
+ {
41773
+ name: "unnecessaryPipe",
41774
+ group: "style",
41775
+ description: "Removes pipe calls with no arguments",
41776
+ defaultSeverity: "suggestion",
41777
+ fixable: true,
41778
+ supportedEffect: [
41779
+ "v3",
41780
+ "v4"
41781
+ ],
41782
+ preview: {
41783
+ sourceText: 'import { pipe } from "effect/Function"\n\nexport const preview = pipe(1)\n',
41784
+ diagnostics: [
41785
+ {
41786
+ start: 63,
41787
+ end: 70,
41788
+ text: "This pipe call contains no arguments. effect(unnecessaryPipe)"
41789
+ }
41790
+ ]
41791
+ }
41792
+ },
41793
+ {
41794
+ name: "unnecessaryPipeChain",
41795
+ group: "style",
41796
+ description: "Simplifies chained pipe calls into a single pipe call",
41797
+ defaultSeverity: "suggestion",
41798
+ fixable: true,
41799
+ supportedEffect: [
41800
+ "v3",
41801
+ "v4"
41802
+ ],
41803
+ preview: {
41804
+ sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.succeed(1).pipe(Effect.asVoid).pipe(Effect.as("done"))\n',
41805
+ diagnostics: [
41806
+ {
41807
+ start: 64,
41808
+ end: 125,
41809
+ text: "Chained pipe calls can be simplified to a single pipe call effect(unnecessaryPipeChain)"
41810
+ }
41811
+ ]
41812
+ }
41813
+ }
41814
+ ]
41815
+ };
41816
+
41817
+ // src/cli/setup/diagnostic-info.ts
41818
+ var diagnosticMetadata = metadata_default;
41819
+ function getDiagnosticGroups() {
41820
+ return diagnosticMetadata.groups;
41821
+ }
41822
+ function getDiagnosticMetadataRules() {
41823
+ return diagnosticMetadata.rules;
41824
+ }
41825
+ function getAllDiagnostics() {
41826
+ const diagnosticsByName = new Map(diagnostics.map((diagnostic) => [diagnostic.name, diagnostic]));
41827
+ return getDiagnosticMetadataRules().flatMap((metadataRule) => {
41828
+ const diagnostic = diagnosticsByName.get(metadataRule.name);
41829
+ if (!diagnostic) {
41830
+ return [];
41831
+ }
41832
+ return [{
41833
+ name: diagnostic.name,
41834
+ code: diagnostic.code,
41835
+ group: metadataRule.group,
41836
+ defaultSeverity: diagnostic.severity,
41837
+ description: metadataRule.description,
41838
+ preview: metadataRule.preview
41839
+ }];
41840
+ });
41841
+ }
41842
+ function cycleSeverity(current, direction) {
41843
+ const order = ["off", "suggestion", "message", "warning", "error"];
41844
+ const currentIndex = order.indexOf(current);
41845
+ if (direction === "right") {
41846
+ return order[(currentIndex + 1) % order.length];
41847
+ } else {
41848
+ return order[(currentIndex - 1 + order.length) % order.length];
41849
+ }
41850
+ }
41851
+ var shortNames = {
41852
+ off: "off",
41853
+ suggestion: "sugg",
41854
+ message: "info",
41855
+ warning: "warn",
41856
+ error: "err"
41857
+ };
41858
+ var MAX_SEVERITY_LENGTH = Object.values(shortNames).reduce((max2, name) => Math.max(max2, name.length), 0);
41859
+ function getSeverityShortName(severity) {
41860
+ return shortNames[severity] ?? "???";
41861
+ }
41862
+
41863
+ // src/cli/setup/diagnostic-prompt.ts
41864
+ var diagnosticGroups = getDiagnosticGroups();
41865
+ var Action2 = taggedEnum();
41866
+ var SEARCH_ICON = "/";
41867
+ var MIN_PREVIEW_AND_MESSAGES_LINES = 18;
41868
+ function getControlsLegend(searchText) {
41869
+ const searchLegend = searchText.length === 0 ? `${SEARCH_ICON} type to search` : `${SEARCH_ICON} searching: ${searchText}`;
41870
+ return `\u2190/\u2192 change rule \u2191/\u2193 change severity ${searchLegend}`;
41871
+ }
41872
+ function getSeveritySymbol(severity) {
41873
+ const symbols = {
41874
+ off: ".",
41875
+ suggestion: "?",
41876
+ message: "i",
41877
+ warning: "!",
41878
+ error: "x"
41879
+ };
41880
+ return symbols[severity];
41881
+ }
41882
+ function getSeverityStyle(severity) {
41883
+ const styles = {
41884
+ off: DIM,
41885
+ suggestion: DIM,
41886
+ message: BLUE,
41887
+ warning: YELLOW,
41888
+ error: RED
41889
+ };
41890
+ return styles[severity];
41891
+ }
41892
+ function renderEntry(entry, severity, isSelected) {
41893
+ const symbol4 = ansi(getSeveritySymbol(severity), getSeverityStyle(severity));
41894
+ const name = isSelected ? ansi(entry.name, UNDERLINE) : entry.name;
41895
+ return `${symbol4} ${name}`;
41896
+ }
41897
+ function rowsForLength(length, columns) {
41898
+ if (columns <= 0) {
41899
+ return 1;
41900
+ }
41901
+ return Math.max(1, 1 + Math.floor(Math.max(length - 1, 0) / columns));
41902
+ }
41903
+ function eraseRenderedLines(lines2, columns) {
41904
+ let result3 = "";
41905
+ for (let lineIndex = lines2.length - 1; lineIndex >= 0; lineIndex--) {
41906
+ const rows = rowsForLength(visibleLength(lines2[lineIndex] ?? ""), columns);
41907
+ for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
41908
+ result3 += ERASE_LINE;
41909
+ if (!(lineIndex === 0 && rowIndex === rows - 1)) {
41910
+ result3 += "\x1B[1A";
41911
+ }
41912
+ }
41913
+ }
41914
+ if (lines2.length > 0) {
41915
+ result3 += CURSOR_LEFT;
41916
+ }
41917
+ return result3;
41918
+ }
41919
+ function wrapPaddedText(text2, initialPadding, endPadding, columns) {
41920
+ if (text2.length === 0) {
41921
+ return [initialPadding + endPadding];
41922
+ }
41923
+ const available = Math.max(columns - visibleLength(initialPadding) - visibleLength(endPadding), 1);
41924
+ const lines2 = [];
41925
+ let remaining = text2;
41926
+ while (remaining.length > 0) {
41927
+ const chunk = remaining.slice(0, available);
41928
+ lines2.push(initialPadding + chunk + endPadding);
41929
+ remaining = remaining.slice(chunk.length);
41930
+ }
41931
+ return lines2;
41932
+ }
41933
+ function wrapListItemText(text2, firstLinePadding, continuationPadding, endPadding, columns) {
41934
+ if (text2.length === 0) {
41935
+ return [firstLinePadding + endPadding];
41936
+ }
41937
+ const firstAvailable = Math.max(columns - visibleLength(firstLinePadding) - visibleLength(endPadding), 1);
41938
+ const continuationAvailable = Math.max(
41939
+ columns - visibleLength(continuationPadding) - visibleLength(endPadding),
41940
+ 1
41941
+ );
41942
+ const lines2 = [];
41943
+ let remaining = text2;
41944
+ let isFirstLine = true;
41945
+ while (remaining.length > 0) {
41946
+ const padding = isFirstLine ? firstLinePadding : continuationPadding;
41947
+ const available = isFirstLine ? firstAvailable : continuationAvailable;
41948
+ const chunk = remaining.slice(0, available);
41949
+ lines2.push(padding + chunk + endPadding);
41950
+ remaining = remaining.slice(chunk.length);
41951
+ isFirstLine = false;
41952
+ }
41953
+ return lines2;
41954
+ }
41955
+ function renderPaddedLine(text2, initialPadding, endPadding, columns) {
41956
+ const available = Math.max(columns - visibleLength(initialPadding) - visibleLength(endPadding), 0);
41957
+ const truncated = visibleLength(text2) <= available ? text2 : text2.slice(0, available);
41958
+ const padding = Math.max(available - visibleLength(truncated), 0);
41959
+ return initialPadding + truncated + " ".repeat(padding) + endPadding;
41960
+ }
41961
+ function mergeHighlightRanges(ranges) {
41962
+ const sorted = ranges.filter((range2) => range2.end > range2.start).slice().sort((a, b) => a.start - b.start);
41963
+ const merged = [];
41964
+ for (const range2 of sorted) {
41965
+ const previous = merged[merged.length - 1];
41966
+ if (!previous || range2.start > previous.end) {
41967
+ merged.push(range2);
41968
+ continue;
41969
+ }
41970
+ merged[merged.length - 1] = {
41971
+ start: previous.start,
41972
+ end: Math.max(previous.end, range2.end)
41973
+ };
41974
+ }
41975
+ return merged;
41976
+ }
41977
+ function stylePreviewLine(line, lineStart, ranges) {
41978
+ if (line.length === 0) {
41979
+ return "";
41980
+ }
41981
+ const lineEnd = lineStart + line.length;
41982
+ let cursor = 0;
41983
+ let rendered = "";
41984
+ for (const range2 of ranges) {
41985
+ const start = Math.max(range2.start, lineStart);
41986
+ const end = Math.min(range2.end, lineEnd);
41987
+ if (end <= start) {
41988
+ continue;
41989
+ }
41990
+ const startIndex = start - lineStart;
41991
+ const endIndex = end - lineStart;
41992
+ if (cursor < startIndex) {
41993
+ rendered += ansi(line.slice(cursor, startIndex), DIM);
41994
+ }
41995
+ rendered += ansi(line.slice(startIndex, endIndex), UNDERLINE);
41996
+ cursor = endIndex;
41997
+ }
41998
+ if (cursor < line.length) {
41999
+ rendered += ansi(line.slice(cursor), DIM);
42000
+ }
42001
+ return rendered;
42002
+ }
42003
+ function wrapSourceLine(line, lineStart, availableWidth) {
42004
+ if (line.length === 0) {
42005
+ return [{ text: "", start: lineStart, end: lineStart }];
42006
+ }
42007
+ const width = Math.max(availableWidth, 1);
42008
+ const wrapped = [];
42009
+ let offset = 0;
42010
+ while (offset < line.length) {
42011
+ const text2 = line.slice(offset, offset + width);
42012
+ wrapped.push({
42013
+ text: text2,
42014
+ start: lineStart + offset,
42015
+ end: lineStart + offset + text2.length
42016
+ });
42017
+ offset += text2.length;
42018
+ }
42019
+ return wrapped;
42020
+ }
42021
+ function wrapPreviewSourceText(sourceText, columns) {
42022
+ const availableWidth = Math.max(columns - 2, 1);
42023
+ const logicalLines = sourceText.split("\n");
42024
+ const wrapped = [];
42025
+ let offset = 0;
42026
+ for (const line of logicalLines) {
42027
+ for (const wrappedLine of wrapSourceLine(line, offset, availableWidth)) {
42028
+ wrapped.push(wrappedLine);
42029
+ }
42030
+ offset += line.length + 1;
42031
+ }
42032
+ return wrapped;
42033
+ }
42034
+ function renderPreviewSourceText(sourceText, diagnostics3, columns) {
42035
+ const ranges = mergeHighlightRanges(diagnostics3.map((diagnostic) => ({
42036
+ start: diagnostic.start,
42037
+ end: diagnostic.end
42038
+ })));
42039
+ return wrapPreviewSourceText(sourceText, columns).map((line) => {
42040
+ const rendered = stylePreviewLine(line.text, line.start, ranges);
42041
+ return CURSOR_TO_0 + renderPaddedLine(rendered, " ", "", columns);
42042
+ });
42043
+ }
42044
+ function renderPreviewMessages(diagnostics3, columns) {
42045
+ const messages = Array.from(new Set(diagnostics3.map((diagnostic) => diagnostic.text)));
42046
+ return messages.flatMap((message) => {
42047
+ let isFirstBlock = true;
42048
+ return message.split("\n").flatMap((line) => {
42049
+ const wrappedLines = wrapListItemText(line, isFirstBlock ? "- " : " ", " ", "", columns);
42050
+ isFirstBlock = false;
42051
+ return wrappedLines.map((wrappedLine) => CURSOR_TO_0 + ansi(wrappedLine, DIM + ITALIC));
42052
+ });
42053
+ });
42054
+ }
42055
+ function renderDivider(columns, legendText) {
42056
+ if (legendText === void 0) {
42057
+ return ansi("\u2500".repeat(Math.max(columns, 0)), DIM);
42058
+ }
42059
+ const legend = ` ${legendText} `;
42060
+ const legendLength = visibleLength(legend);
42061
+ if (columns <= legendLength) {
42062
+ return ansi(legend.slice(0, Math.max(columns, 0)), DIM);
42063
+ }
42064
+ const remaining = columns - legendLength;
42065
+ const left = "\u2500".repeat(Math.floor(remaining / 2));
42066
+ const right = "\u2500".repeat(remaining - left.length);
42067
+ return ansi(left + legend + right, DIM);
42068
+ }
42069
+ function renderSelectedRuleDivider(selected, severities, columns) {
42070
+ if (!selected) {
42071
+ return renderDivider(columns);
42072
+ }
42073
+ const currentSeverity = severities[selected.name] ?? selected.defaultSeverity;
42074
+ const text2 = `${selected.name} (currently set as ${getSeverityShortName(currentSeverity)})`;
42075
+ return renderDivider(columns, text2);
42076
+ }
42077
+ function matchesSearch(entry, searchText) {
42078
+ if (searchText.length === 0) {
42079
+ return true;
42080
+ }
42081
+ const normalized = searchText.toLowerCase();
42082
+ return entry.name.toLowerCase().includes(normalized) || entry.description.toLowerCase().includes(normalized) || entry.previewSourceText.toLowerCase().includes(normalized);
42083
+ }
42084
+ function getFilteredEntries(entries2, searchText) {
42085
+ return entries2.filter((entry) => matchesSearch(entry, searchText));
42086
+ }
42087
+ function normalizeStartIndex(length, startIndex) {
42088
+ if (length <= 0) {
42089
+ return 0;
42090
+ }
42091
+ return (startIndex % length + length) % length;
42092
+ }
42093
+ function isPrintableInput(input) {
42094
+ const printablePattern = new RegExp(String.raw`^[^\u0000-\u001F\u007F]+$`, "u");
42095
+ return !input.key.ctrl && !input.key.meta && input.input !== void 0 && input.input.length > 0 && printablePattern.test(input.input);
42096
+ }
42097
+ function buildVisibleEntries(entries2, severities, startIndex, columns) {
42098
+ if (entries2.length === 0) {
42099
+ return {
42100
+ fullLine: renderPaddedLine(ansi("No matching rules", DIM), " ", " ", columns),
42101
+ visibleRules: []
42102
+ };
42103
+ }
42104
+ const reservedColumns = 4;
42105
+ const itemColumns = Math.max(columns - reservedColumns, 0);
42106
+ const separator = " ";
42107
+ const visibleEntries = [];
42108
+ const visibleRules = [];
42109
+ let currentLength = 0;
42110
+ let seenCount = 0;
42111
+ while (seenCount < entries2.length) {
42112
+ const index = (startIndex + seenCount) % entries2.length;
42113
+ const rule = entries2[index];
42114
+ const currentSeverity = severities[rule.name] ?? rule.defaultSeverity;
42115
+ const entry = renderEntry(rule, currentSeverity, seenCount === 0);
42116
+ const nextLength = visibleEntries.length === 0 ? visibleLength(entry) : currentLength + separator.length + visibleLength(entry);
42117
+ if (nextLength > itemColumns) {
42118
+ break;
42119
+ }
42120
+ visibleEntries.push(entry);
42121
+ visibleRules.push(rule);
42122
+ currentLength = nextLength;
42123
+ seenCount++;
42124
+ }
42125
+ const leftMarker = entries2.length > 1 ? ansi("\u2190", DIM) : " ";
42126
+ const rightMarker = entries2.length > 1 ? ansi("\u2192", DIM) : " ";
42127
+ return {
42128
+ fullLine: renderPaddedLine(visibleEntries.join(separator), `${leftMarker} `, ` ${rightMarker}`, columns),
42129
+ visibleRules
42130
+ };
42131
+ }
42132
+ function buildGroupLine(selected, columns) {
42133
+ if (!selected) {
42134
+ return renderPaddedLine("", " ", " ", columns);
42135
+ }
42136
+ const selectedIndex = diagnosticGroups.findIndex((group) => group.id === selected.group);
42137
+ const rotatedGroups = diagnosticGroups.map(
42138
+ (_, index) => diagnosticGroups[(selectedIndex + index) % diagnosticGroups.length]
42139
+ );
42140
+ const content = rotatedGroups.map((group, index) => {
42141
+ const label = group.name.toUpperCase();
42142
+ return index === 0 ? ansi(label, BOLD) : ansi(label, DIM);
42143
+ }).join(" ");
42144
+ return renderPaddedLine(content, " ", " ", columns);
42145
+ }
42146
+ function buildFrame(entries2, searchText, severities, startIndex, columns) {
42147
+ const visible = buildVisibleEntries(entries2, severities, startIndex, columns);
42148
+ const selected = visible.visibleRules[0];
42149
+ const wrappedDescription = wrapPaddedText(selected?.description ?? "", " ", "", columns);
42150
+ const previewLines = renderPreviewSourceText(
42151
+ selected?.previewSourceText ?? "",
42152
+ selected?.previewDiagnostics ?? [],
42153
+ columns
42154
+ );
42155
+ const previewMessages = renderPreviewMessages(selected?.previewDiagnostics ?? [], columns);
42156
+ const previewSectionLines = [...previewLines, ...previewMessages];
42157
+ const paddingLines = Array.from(
42158
+ { length: Math.max(MIN_PREVIEW_AND_MESSAGES_LINES - previewSectionLines.length, 0) },
42159
+ () => ""
42160
+ );
42161
+ return [
42162
+ CURSOR_HIDE + renderSelectedRuleDivider(selected, severities, columns),
42163
+ ...previewSectionLines,
42164
+ ...paddingLines,
42165
+ CURSOR_TO_0 + renderDivider(columns),
42166
+ CURSOR_TO_0 + buildGroupLine(selected, columns),
42167
+ CURSOR_TO_0 + visible.fullLine,
42168
+ ...wrappedDescription.map((line) => CURSOR_TO_0 + ansi(line, DIM)),
42169
+ CURSOR_TO_0 + renderDivider(columns, getControlsLegend(searchText)),
42170
+ ""
42171
+ ];
42172
+ }
42173
+ function buildState(entries2, startIndex, searchText, severities) {
42174
+ return gen2(function* () {
42175
+ const terminal = yield* Terminal;
42176
+ const columns = Math.max(yield* terminal.columns, 1);
42177
+ const filteredEntries = getFilteredEntries(entries2, searchText);
42178
+ const normalizedStartIndex = normalizeStartIndex(filteredEntries.length, startIndex);
42179
+ const renderedLines = buildFrame(filteredEntries, searchText, severities, normalizedStartIndex, columns);
42180
+ return {
42181
+ startIndex: normalizedStartIndex,
42182
+ searchText,
42183
+ severities,
42184
+ renderedColumns: columns,
42185
+ renderedLines
42186
+ };
42187
+ });
42188
+ }
42189
+ function renderSubmission(state, entries2) {
42190
+ return succeed6(
42191
+ CURSOR_TO_0 + JSON.stringify(
42192
+ Object.fromEntries(
42193
+ entries2.flatMap((entry) => {
42194
+ const severity = state.severities[entry.name];
42195
+ return severity !== void 0 && severity !== entry.defaultSeverity ? [[entry.name, severity]] : [];
42196
+ })
42197
+ )
42198
+ ) + "\n"
42199
+ );
42200
+ }
42201
+ function handleProcess(entries2) {
42202
+ return (input, state) => {
42203
+ const filteredEntries = getFilteredEntries(entries2, state.searchText);
42204
+ switch (input.key.name) {
42205
+ case "backspace":
42206
+ return buildState(entries2, 0, state.searchText.slice(0, -1), state.severities).pipe(
42207
+ map6((nextState) => Action2.NextFrame({ state: nextState }))
42208
+ );
42209
+ case "left":
42210
+ if (filteredEntries.length === 0) return succeed6(Action2.Beep());
42211
+ return buildState(entries2, state.startIndex - 1, state.searchText, state.severities).pipe(
42212
+ map6((nextState) => Action2.NextFrame({ state: nextState }))
42213
+ );
42214
+ case "right":
42215
+ if (filteredEntries.length === 0) return succeed6(Action2.Beep());
42216
+ return buildState(entries2, state.startIndex + 1, state.searchText, state.severities).pipe(
42217
+ map6((nextState) => Action2.NextFrame({ state: nextState }))
42218
+ );
42219
+ case "up":
42220
+ case "down":
42221
+ if (filteredEntries.length === 0) return succeed6(Action2.Beep());
42222
+ return buildState(entries2, state.startIndex, state.searchText, {
42223
+ ...state.severities,
42224
+ [filteredEntries[state.startIndex].name]: cycleSeverity(
42225
+ state.severities[filteredEntries[state.startIndex].name] ?? filteredEntries[state.startIndex].defaultSeverity,
42226
+ input.key.name === "up" ? "left" : "right"
42227
+ )
42228
+ }).pipe(map6((nextState) => Action2.NextFrame({ state: nextState })));
42229
+ case "enter":
42230
+ case "return":
42231
+ return succeed6(Action2.Submit({
42232
+ value: Object.fromEntries(
42233
+ entries2.flatMap((entry) => {
42234
+ const severity = state.severities[entry.name];
42235
+ return severity !== void 0 && severity !== entry.defaultSeverity ? [[entry.name, severity]] : [];
42236
+ })
42237
+ )
42238
+ }));
42239
+ default:
42240
+ if (!isPrintableInput(input)) return succeed6(Action2.Beep());
42241
+ return buildState(entries2, 0, state.searchText + input.input, state.severities).pipe(
42242
+ map6((nextState) => Action2.NextFrame({ state: nextState }))
42243
+ );
42244
+ }
42245
+ };
42246
+ }
42247
+ function getPromptEntries(diagnostics3) {
42248
+ const diagnosticsByName = new Map(diagnostics3.map((diagnostic) => [diagnostic.name, diagnostic]));
42249
+ return diagnostics3.flatMap((rule) => {
42250
+ const diagnostic = diagnosticsByName.get(rule.name);
42251
+ if (!diagnostic) {
42252
+ return [];
42253
+ }
42254
+ return [{
42255
+ name: rule.name,
42256
+ group: diagnostic.group,
42257
+ description: diagnostic.description,
42258
+ previewSourceText: diagnostic.preview.sourceText,
42259
+ previewDiagnostics: diagnostic.preview.diagnostics,
42260
+ defaultSeverity: diagnostic.defaultSeverity
42261
+ }];
42262
+ });
42263
+ }
42264
+ function createDiagnosticPrompt(diagnostics3, initialSeverities) {
42265
+ const entries2 = getPromptEntries(diagnostics3);
42266
+ return custom(buildState(entries2, 0, "", initialSeverities), {
42267
+ render: (state, action2) => {
42268
+ switch (action2._tag) {
42269
+ case "Beep":
42270
+ return succeed6(BEEP);
42271
+ case "NextFrame":
42272
+ return succeed6(action2.state.renderedLines.join("\n"));
42273
+ case "Submit":
42274
+ return renderSubmission(state, entries2);
42275
+ }
42276
+ },
42277
+ process: handleProcess(entries2),
42278
+ clear: (state) => gen2(function* () {
42279
+ const terminal = yield* Terminal;
42280
+ const columns = yield* terminal.columns;
42281
+ return eraseRenderedLines(state.renderedLines, columns);
42282
+ })
40535
42283
  });
40536
42284
  }
40537
42285