@effect/tsgo 0.0.15 → 0.0.17

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.
Files changed (3) hide show
  1. package/README.md +179 -0
  2. package/bin/effect-tsgo.js +1133 -326
  3. package/package.json +11 -10
@@ -34157,14 +34157,14 @@ const defaultFormatter = (options) => {
34157
34157
  * Strips ANSI escape codes from a string to calculate visual width.
34158
34158
  * @internal
34159
34159
  */
34160
- const stripAnsi = (text) => {
34160
+ const stripAnsi$1 = (text) => {
34161
34161
  return text.replace(/\u001B\[[0-9;]*m/g, "");
34162
34162
  };
34163
34163
  /**
34164
34164
  * Gets the visual length of a string (excluding ANSI codes).
34165
34165
  * @internal
34166
34166
  */
34167
- const visualLength = (text) => stripAnsi(text).length;
34167
+ const visualLength = (text) => stripAnsi$1(text).length;
34168
34168
  /**
34169
34169
  * Helper function to pad strings to a specified width.
34170
34170
  * @internal
@@ -45596,7 +45596,7 @@ const cursorMove = (column, row = 0) => {
45596
45596
  return command;
45597
45597
  };
45598
45598
  /** @internal */
45599
- const eraseLines$1 = (rows) => {
45599
+ const eraseLines = (rows) => {
45600
45600
  let command = "";
45601
45601
  for (let i = 0; i < rows; i++) command += `${ESC}2K` + (i < rows - 1 ? `${ESC}1A` : "");
45602
45602
  if (rows > 0) command += `${ESC}G`;
@@ -45812,20 +45812,20 @@ const Action$1 = /* @__PURE__ */ taggedEnum();
45812
45812
  /**
45813
45813
  * Clears all lines taken up by the specified `text`.
45814
45814
  */
45815
- const eraseText$1 = (text, columns) => {
45815
+ const eraseText = (text, columns) => {
45816
45816
  if (columns === 0) return eraseLine + cursorTo(0);
45817
45817
  let rows = 0;
45818
45818
  const lines = text.split(NEWLINE_REGEXP);
45819
45819
  for (const line of lines) rows += 1 + Math.floor(Math.max(line.length - 1, 0) / columns);
45820
- return eraseLines$1(rows);
45820
+ return eraseLines(rows);
45821
45821
  };
45822
45822
  const lines = (prompt, columns) => {
45823
45823
  const lines = prompt.split(NEWLINE_REGEXP);
45824
45824
  return columns === 0 ? lines.length : pipe(map$7(lines, (line) => Math.ceil(line.length / columns)), reduce(0, (left, right) => left + right));
45825
45825
  };
45826
45826
  const clearOutputWithError = (outputText, columns, errorText) => {
45827
- if (errorText !== void 0 && errorText.length > 0) return cursorDown(lines(errorText, columns)) + eraseText$1(`\n${errorText}`, columns) + eraseText$1(outputText, columns);
45828
- return eraseText$1(outputText, columns);
45827
+ if (errorText !== void 0 && errorText.length > 0) return cursorDown(lines(errorText, columns)) + eraseText(`\n${errorText}`, columns) + eraseText(outputText, columns);
45828
+ return eraseText(outputText, columns);
45829
45829
  };
45830
45830
  const renderBeep = beep;
45831
45831
  const NEWLINE_REGEXP = /\r?\n/;
@@ -45833,7 +45833,7 @@ const handleConfirmClear = (options) => {
45833
45833
  return fnUntraced(function* (state, _) {
45834
45834
  const columns = yield* (yield* Terminal).columns;
45835
45835
  const figures = yield* platformFigures;
45836
- return eraseText$1(renderConfirmOutput(state.value ? options.placeholder.defaultConfirm : options.placeholder.defaultDeny, "?", figures.pointerSmall, options, { plain: true }), columns) + (eraseLine + cursorLeft);
45836
+ return eraseText(renderConfirmOutput(state.value ? options.placeholder.defaultConfirm : options.placeholder.defaultDeny, "?", figures.pointerSmall, options, { plain: true }), columns) + (eraseLine + cursorLeft);
45837
45837
  });
45838
45838
  };
45839
45839
  const renderConfirmOutput = (confirm, leadingSymbol, trailingSymbol, options, renderOptions) => renderPrompt(confirm, options.message, leadingSymbol, trailingSymbol, renderOptions);
@@ -45903,7 +45903,7 @@ const renderMultiSelectChoices = (state, options, figures, renderOptions) => {
45903
45903
  const inverseSelectionText = options?.inverseSelection ?? "Inverse Selection";
45904
45904
  const metaOptions = [{ title: selectAllText }, { title: inverseSelectionText }];
45905
45905
  const allChoices = [...metaOptions, ...choices];
45906
- const toDisplay = entriesToDisplay$1(state.index, allChoices.length, options.maxPerPage);
45906
+ const toDisplay = entriesToDisplay(state.index, allChoices.length, options.maxPerPage);
45907
45907
  const documents = [];
45908
45908
  for (let index = toDisplay.startIndex; index < toDisplay.endIndex; index++) {
45909
45909
  const choice = allChoices[index];
@@ -46039,7 +46039,7 @@ const renderChoiceTitle = (choice, isSelected, renderOptions) => {
46039
46039
  };
46040
46040
  const renderSelectChoices = (state, options, figures, renderOptions) => {
46041
46041
  const choices = options.choices;
46042
- const toDisplay = entriesToDisplay$1(state, choices.length, options.maxPerPage);
46042
+ const toDisplay = entriesToDisplay(state, choices.length, options.maxPerPage);
46043
46043
  const documents = [];
46044
46044
  for (let index = toDisplay.startIndex; index < toDisplay.endIndex; index++) {
46045
46045
  const choice = choices[index];
@@ -46086,7 +46086,7 @@ const handleSelectClear = (options) => fnUntraced(function* (state, _) {
46086
46086
  const columns = yield* (yield* Terminal).columns;
46087
46087
  const figures = yield* platformFigures;
46088
46088
  const clearPrompt = eraseLine + cursorLeft;
46089
- return eraseText$1(`${renderSelectOutput("?", figures.pointerSmall, options, { plain: true })}\n${renderSelectChoices(state, options, figures, { plain: true })}`, columns) + clearPrompt;
46089
+ return eraseText(`${renderSelectOutput("?", figures.pointerSmall, options, { plain: true })}\n${renderSelectChoices(state, options, figures, { plain: true })}`, columns) + clearPrompt;
46090
46090
  });
46091
46091
  const handleSelectProcess = (options) => {
46092
46092
  return (input, state) => {
@@ -46262,7 +46262,7 @@ const basePrompt = (options, type) => {
46262
46262
  clear: handleTextClear(opts)
46263
46263
  });
46264
46264
  };
46265
- const entriesToDisplay$1 = (cursor, total, maxVisible) => {
46265
+ const entriesToDisplay = (cursor, total, maxVisible) => {
46266
46266
  const max = maxVisible === void 0 ? total : maxVisible;
46267
46267
  let startIndex = Math.min(total - max, cursor - Math.floor(max / 2));
46268
46268
  if (startIndex < 0) startIndex = 0;
@@ -195732,7 +195732,7 @@ var FileReadError = class extends TaggedError("FileReadError") {
195732
195732
  //#endregion
195733
195733
  //#region package.json
195734
195734
  var name = "@effect/tsgo";
195735
- var version = "0.0.15";
195735
+ var version = "0.0.17";
195736
195736
 
195737
195737
  //#endregion
195738
195738
  //#region src/setup/consts.ts
@@ -196077,7 +196077,43 @@ const computeTsConfigChanges = (current, target, lspVersion) => {
196077
196077
  const rootObj = getRootObject(current.sourceFile);
196078
196078
  if (!rootObj) return emptyFileChangesResult();
196079
196079
  const compilerOptionsProperty = findPropertyInObject(rootObj, "compilerOptions");
196080
- if (!compilerOptionsProperty || !import_typescript.isObjectLiteralExpression(compilerOptionsProperty.initializer)) return emptyFileChangesResult();
196080
+ if (!compilerOptionsProperty || !import_typescript.isObjectLiteralExpression(compilerOptionsProperty.initializer)) {
196081
+ if (isNone(lspVersion)) return emptyFileChangesResult();
196082
+ const ctx = createTrackerContext();
196083
+ const fileChange = tsInternal.textChanges.ChangeTracker.with(ctx, (tracker) => {
196084
+ const schemaProperty = findPropertyInObject(rootObj, "$schema");
196085
+ const shouldAddSchema = !schemaProperty;
196086
+ const shouldUpdateSchema = !!schemaProperty && (!import_typescript.isStringLiteral(schemaProperty.initializer) || schemaProperty.initializer.text !== TSCONFIG_SCHEMA_URL);
196087
+ if (shouldAddSchema) descriptions.push("Add $schema to tsconfig");
196088
+ else if (shouldUpdateSchema) descriptions.push("Update $schema in tsconfig");
196089
+ descriptions.push(`Add compilerOptions with ${LSP_PLUGIN_NAME} plugin`);
196090
+ const schemaPropertyAssignment = import_typescript.factory.createPropertyAssignment(import_typescript.factory.createStringLiteral("$schema"), import_typescript.factory.createStringLiteral(TSCONFIG_SCHEMA_URL));
196091
+ const compilerOptionsAssignment = import_typescript.factory.createPropertyAssignment(import_typescript.factory.createStringLiteral("compilerOptions"), import_typescript.factory.createObjectLiteralExpression([import_typescript.factory.createPropertyAssignment(import_typescript.factory.createStringLiteral("plugins"), import_typescript.factory.createArrayLiteralExpression([createLspPluginObject(target)], true))], true));
196092
+ const nextProperties = rootObj.properties.map((property) => {
196093
+ if (schemaProperty && property === schemaProperty) return schemaPropertyAssignment;
196094
+ return property;
196095
+ });
196096
+ if (shouldAddSchema) nextProperties.push(schemaPropertyAssignment);
196097
+ nextProperties.push(compilerOptionsAssignment);
196098
+ tracker.replaceNode(current.sourceFile, rootObj, import_typescript.factory.createObjectLiteralExpression(nextProperties, true));
196099
+ }).find((fc) => fc.fileName === current.sourceFile.fileName);
196100
+ const changes = fileChange ? fileChange.textChanges : [];
196101
+ if (changes.length === 0) return {
196102
+ codeActions: [],
196103
+ messages
196104
+ };
196105
+ return {
196106
+ codeActions: [{
196107
+ description: descriptions.join("; "),
196108
+ changes: [{
196109
+ fileName: current.sourceFile.fileName,
196110
+ textChanges: changes,
196111
+ isNewFile: false
196112
+ }]
196113
+ }],
196114
+ messages
196115
+ };
196116
+ }
196081
196117
  const compilerOptions = compilerOptionsProperty.initializer;
196082
196118
  const ctx = createTrackerContext();
196083
196119
  const fileChange = tsInternal.textChanges.ChangeTracker.with(ctx, (tracker) => {
@@ -196420,314 +196456,922 @@ const renderCodeActions = (result, assessmentState) => gen(function* () {
196420
196456
  });
196421
196457
 
196422
196458
  //#endregion
196423
- //#region src/rules.json
196424
- var rules_default = [
196459
+ //#region src/metadata.json
196460
+ var groups = [
196425
196461
  {
196426
- "name": "anyUnknownInErrorContext",
196427
- "description": "Detects 'any' or 'unknown' types in Effect error or requirements channels",
196428
- "defaultSeverity": "off",
196429
- "codes": [377030]
196462
+ "id": "correctness",
196463
+ "name": "Correctness",
196464
+ "description": "Wrong, unsafe, or structurally invalid code patterns."
196430
196465
  },
196431
196466
  {
196432
- "name": "catchAllToMapError",
196433
- "description": "Suggests using Effect.mapError instead of Effect.catch + Effect.fail",
196434
- "defaultSeverity": "suggestion",
196435
- "codes": [377010]
196467
+ "id": "antipattern",
196468
+ "name": "Anti-pattern",
196469
+ "description": "Discouraged patterns that often lead to bugs or confusing behavior."
196436
196470
  },
196437
196471
  {
196438
- "name": "catchUnfailableEffect",
196439
- "description": "Warns when using error handling on Effects that never fail",
196440
- "defaultSeverity": "suggestion",
196441
- "codes": [377009]
196472
+ "id": "effectNative",
196473
+ "name": "Effect-native",
196474
+ "description": "Prefer Effect-native APIs and abstractions when available."
196442
196475
  },
196443
196476
  {
196444
- "name": "classSelfMismatch",
196445
- "description": "Ensures Self type parameter matches the class name in Service/Tag/Schema classes",
196446
- "defaultSeverity": "error",
196447
- "codes": [377046]
196448
- },
196477
+ "id": "style",
196478
+ "name": "Style",
196479
+ "description": "Cleanup, consistency, and idiomatic Effect code."
196480
+ }
196481
+ ];
196482
+ var rules = [
196449
196483
  {
196450
- "name": "deterministicKeys",
196451
- "description": "Enforces deterministic naming for service/tag/error identifiers based on class names",
196484
+ "name": "anyUnknownInErrorContext",
196485
+ "group": "correctness",
196486
+ "description": "Detects 'any' or 'unknown' types in Effect error or requirements channels",
196452
196487
  "defaultSeverity": "off",
196453
- "codes": [377049]
196488
+ "fixable": false,
196489
+ "supportedEffect": ["v3", "v4"],
196490
+ "codes": [377030],
196491
+ "preview": {
196492
+ "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",
196493
+ "diagnostics": [
196494
+ {
196495
+ "start": 46,
196496
+ "end": 53,
196497
+ "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)"
196498
+ },
196499
+ {
196500
+ "start": 90,
196501
+ "end": 116,
196502
+ "text": "This has unknown in the requirements channel which is not recommended.\nOnly service identifiers should appear in the requirements channel. effect(anyUnknownInErrorContext)"
196503
+ },
196504
+ {
196505
+ "start": 133,
196506
+ "end": 157,
196507
+ "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)"
196508
+ }
196509
+ ]
196510
+ }
196511
+ },
196512
+ {
196513
+ "name": "classSelfMismatch",
196514
+ "group": "correctness",
196515
+ "description": "Ensures Self type parameter matches the class name in ServiceMap/Service/Tag/Schema classes",
196516
+ "defaultSeverity": "error",
196517
+ "fixable": true,
196518
+ "supportedEffect": ["v3", "v4"],
196519
+ "codes": [377046],
196520
+ "preview": {
196521
+ "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",
196522
+ "diagnostics": [{
196523
+ "start": 154,
196524
+ "end": 169,
196525
+ "text": "Self type parameter should be 'InvalidContextTag'. effect(classSelfMismatch)"
196526
+ }]
196527
+ }
196454
196528
  },
196455
196529
  {
196456
196530
  "name": "duplicatePackage",
196531
+ "group": "correctness",
196457
196532
  "description": "Warns when multiple versions of an Effect-related package are detected in the program",
196458
196533
  "defaultSeverity": "warning",
196459
- "codes": [377051]
196534
+ "fixable": false,
196535
+ "supportedEffect": ["v3", "v4"],
196536
+ "codes": [377051],
196537
+ "preview": {
196538
+ "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",
196539
+ "diagnostics": []
196540
+ }
196460
196541
  },
196461
196542
  {
196462
- "name": "effectFnIife",
196463
- "description": "Effect.fn or Effect.fnUntraced is called as an IIFE; use Effect.gen instead",
196543
+ "name": "floatingEffect",
196544
+ "group": "correctness",
196545
+ "description": "Detects Effect values that are neither yielded nor assigned",
196546
+ "defaultSeverity": "error",
196547
+ "fixable": false,
196548
+ "supportedEffect": ["v3", "v4"],
196549
+ "codes": [377001, 377058],
196550
+ "preview": {
196551
+ "sourceText": "import * as Effect from \"effect/Effect\"\n\nEffect.log(\"forgotten\")\n",
196552
+ "diagnostics": [{
196553
+ "start": 41,
196554
+ "end": 64,
196555
+ "text": "Effect must be yielded or assigned to a variable. effect(floatingEffect)"
196556
+ }]
196557
+ }
196558
+ },
196559
+ {
196560
+ "name": "genericEffectServices",
196561
+ "group": "correctness",
196562
+ "description": "Prevents services with type parameters that cannot be discriminated at runtime",
196464
196563
  "defaultSeverity": "warning",
196465
- "codes": [377011]
196564
+ "fixable": false,
196565
+ "supportedEffect": ["v3", "v4"],
196566
+ "codes": [377043],
196567
+ "preview": {
196568
+ "sourceText": "import { Effect } from \"effect\"\n\nexport class Preview<_A> extends Effect.Service<Preview<any>>()(\"Preview\", {\n succeed: {}\n}) {}\n",
196569
+ "diagnostics": [{
196570
+ "start": 46,
196571
+ "end": 53,
196572
+ "text": "Effect Services with type parameters are not supported because they cannot be properly discriminated at runtime, which may cause unexpected behavior. effect(genericEffectServices)"
196573
+ }]
196574
+ }
196466
196575
  },
196467
196576
  {
196468
- "name": "effectFnOpportunity",
196469
- "description": "Suggests using Effect.fn for functions that return an Effect",
196470
- "defaultSeverity": "suggestion",
196471
- "codes": [377047]
196577
+ "name": "missingEffectContext",
196578
+ "group": "correctness",
196579
+ "description": "Detects Effect values with unhandled context requirements",
196580
+ "defaultSeverity": "error",
196581
+ "fixable": false,
196582
+ "supportedEffect": ["v3", "v4"],
196583
+ "codes": [377004],
196584
+ "preview": {
196585
+ "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",
196586
+ "diagnostics": [{
196587
+ "start": 160,
196588
+ "end": 167,
196589
+ "text": "Missing context Db in the expected Effect type. effect(missingEffectContext)"
196590
+ }]
196591
+ }
196472
196592
  },
196473
196593
  {
196474
- "name": "effectGenUsesAdapter",
196475
- "description": "Warns when using the deprecated adapter parameter in Effect.gen",
196476
- "defaultSeverity": "warning",
196477
- "codes": [377027]
196594
+ "name": "missingEffectError",
196595
+ "group": "correctness",
196596
+ "description": "Detects Effect values with unhandled error types",
196597
+ "defaultSeverity": "error",
196598
+ "fixable": true,
196599
+ "supportedEffect": ["v3", "v4"],
196600
+ "codes": [377003],
196601
+ "preview": {
196602
+ "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",
196603
+ "diagnostics": [{
196604
+ "start": 256,
196605
+ "end": 262,
196606
+ "text": "Missing errors Boom in the expected Effect type. effect(missingEffectError)"
196607
+ }]
196608
+ }
196478
196609
  },
196479
196610
  {
196480
- "name": "effectInFailure",
196481
- "description": "Warns when an Effect is used inside an Effect failure channel",
196482
- "defaultSeverity": "warning",
196483
- "codes": [377054]
196611
+ "name": "missingLayerContext",
196612
+ "group": "correctness",
196613
+ "description": "Detects Layer values with unhandled context requirements",
196614
+ "defaultSeverity": "error",
196615
+ "fixable": false,
196616
+ "supportedEffect": ["v3", "v4"],
196617
+ "codes": [377034],
196618
+ "preview": {
196619
+ "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",
196620
+ "diagnostics": [{
196621
+ "start": 259,
196622
+ "end": 266,
196623
+ "text": "Missing 'A' in the expected Layer context. effect(missingLayerContext)"
196624
+ }]
196625
+ }
196484
196626
  },
196485
196627
  {
196486
- "name": "effectInVoidSuccess",
196487
- "description": "Detects nested Effects in void success channels that may cause unexecuted effects",
196628
+ "name": "missingReturnYieldStar",
196629
+ "group": "correctness",
196630
+ "description": "Suggests using return yield* for Effects that never succeed",
196631
+ "defaultSeverity": "error",
196632
+ "fixable": true,
196633
+ "supportedEffect": ["v3", "v4"],
196634
+ "codes": [377006],
196635
+ "preview": {
196636
+ "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",
196637
+ "diagnostics": [{
196638
+ "start": 121,
196639
+ "end": 126,
196640
+ "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)"
196641
+ }]
196642
+ }
196643
+ },
196644
+ {
196645
+ "name": "missingStarInYieldEffectGen",
196646
+ "group": "correctness",
196647
+ "description": "Detects bare yield (without *) inside Effect generator scopes",
196648
+ "defaultSeverity": "error",
196649
+ "fixable": true,
196650
+ "supportedEffect": ["v3", "v4"],
196651
+ "codes": [377007, 377008],
196652
+ "preview": {
196653
+ "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",
196654
+ "diagnostics": [{
196655
+ "start": 105,
196656
+ "end": 110,
196657
+ "text": "When yielding Effects inside Effect.gen, you should use yield* instead of yield. effect(missingStarInYieldEffectGen)"
196658
+ }]
196659
+ }
196660
+ },
196661
+ {
196662
+ "name": "nonObjectEffectServiceType",
196663
+ "group": "correctness",
196664
+ "description": "Ensures Effect.Service types are objects, not primitives",
196665
+ "defaultSeverity": "error",
196666
+ "fixable": false,
196667
+ "supportedEffect": ["v3"],
196668
+ "codes": [377048],
196669
+ "preview": {
196670
+ "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",
196671
+ "diagnostics": [{
196672
+ "start": 142,
196673
+ "end": 149,
196674
+ "text": "Effect.Service requires the service type to be an object {} and not a primitive type. Consider wrapping the value in an object, or manually using Context.Tag or Effect.Tag if you want to use a primitive instead. effect(nonObjectEffectServiceType)"
196675
+ }]
196676
+ }
196677
+ },
196678
+ {
196679
+ "name": "outdatedApi",
196680
+ "group": "correctness",
196681
+ "description": "Detects usage of APIs that have been removed or renamed in Effect v4",
196488
196682
  "defaultSeverity": "warning",
196489
- "codes": [377020]
196683
+ "fixable": false,
196684
+ "supportedEffect": ["v4"],
196685
+ "codes": [377052, 377053],
196686
+ "preview": {
196687
+ "sourceText": "import { Effect } from \"effect\"\n\nexport const preview = Effect.gen(function*() {\n // @ts-expect-error\n return yield* Effect.runtime()\n})\n",
196688
+ "diagnostics": [{
196689
+ "start": 0,
196690
+ "end": 0,
196691
+ "text": "This project targets Effect v4, but is using Effect v3 APIs. To find the correct API to use, consult the Effect v4 documentation for the corresponding v4 replacement. effect(outdatedApi)"
196692
+ }, {
196693
+ "start": 126,
196694
+ "end": 133,
196695
+ "text": "runtime is an Effect v3 API, but the project is using Effect v4. Runtime has been removed in Effect v4. Use Effect.services to grab services and then run using Effect.runPromiseWith. effect(outdatedApi)"
196696
+ }]
196697
+ }
196490
196698
  },
196491
196699
  {
196492
- "name": "effectMapVoid",
196493
- "description": "Suggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {})",
196494
- "defaultSeverity": "suggestion",
196495
- "codes": [377018]
196700
+ "name": "overriddenSchemaConstructor",
196701
+ "group": "correctness",
196702
+ "description": "Prevents overriding constructors in Schema classes which breaks decoding behavior",
196703
+ "defaultSeverity": "error",
196704
+ "fixable": true,
196705
+ "supportedEffect": ["v3", "v4"],
196706
+ "codes": [377044],
196707
+ "preview": {
196708
+ "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",
196709
+ "diagnostics": [{
196710
+ "start": 123,
196711
+ "end": 134,
196712
+ "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)"
196713
+ }]
196714
+ }
196496
196715
  },
196497
196716
  {
196498
- "name": "effectSucceedWithVoid",
196499
- "description": "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
196717
+ "name": "catchUnfailableEffect",
196718
+ "group": "antipattern",
196719
+ "description": "Warns when using error handling on Effects that never fail",
196500
196720
  "defaultSeverity": "suggestion",
196501
- "codes": [377016]
196721
+ "fixable": false,
196722
+ "supportedEffect": ["v3", "v4"],
196723
+ "codes": [377009],
196724
+ "preview": {
196725
+ "sourceText": "import { Effect } from \"effect\"\n\nexport const preview = Effect.succeed(1).pipe(\n Effect.catch(() => Effect.succeed(0))\n)\n",
196726
+ "diagnostics": [{
196727
+ "start": 82,
196728
+ "end": 94,
196729
+ "text": "Looks like the previous effect never fails, so probably this error handling will never be triggered. effect(catchUnfailableEffect)"
196730
+ }]
196731
+ }
196502
196732
  },
196503
196733
  {
196504
- "name": "extendsNativeError",
196505
- "description": "Warns when a class directly extends the native Error class",
196506
- "defaultSeverity": "off",
196507
- "codes": [377055]
196734
+ "name": "effectFnIife",
196735
+ "group": "antipattern",
196736
+ "description": "Effect.fn or Effect.fnUntraced is called as an IIFE; use Effect.gen instead",
196737
+ "defaultSeverity": "warning",
196738
+ "fixable": true,
196739
+ "supportedEffect": ["v3", "v4"],
196740
+ "codes": [377011],
196741
+ "preview": {
196742
+ "sourceText": "import * as Effect from \"effect/Effect\"\n\nexport const preview = Effect.fn(\"preview\")(function*() {\n return yield* Effect.succeed(1)\n})()\n",
196743
+ "diagnostics": [{
196744
+ "start": 64,
196745
+ "end": 137,
196746
+ "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 maintain tracing spans. effect(effectFnIife)"
196747
+ }]
196748
+ }
196508
196749
  },
196509
196750
  {
196510
- "name": "floatingEffect",
196511
- "description": "Detects Effect values that are neither yielded nor assigned",
196512
- "defaultSeverity": "error",
196513
- "codes": [377001, 377058]
196751
+ "name": "effectGenUsesAdapter",
196752
+ "group": "antipattern",
196753
+ "description": "Warns when using the deprecated adapter parameter in Effect.gen",
196754
+ "defaultSeverity": "warning",
196755
+ "fixable": false,
196756
+ "supportedEffect": ["v3", "v4"],
196757
+ "codes": [377027],
196758
+ "preview": {
196759
+ "sourceText": "import * as Effect from \"effect/Effect\"\n\nexport const preview = Effect.gen(function*(_) {\n return yield* Effect.succeed(1)\n})\n",
196760
+ "diagnostics": [{
196761
+ "start": 85,
196762
+ "end": 86,
196763
+ "text": "The adapter of Effect.gen is not required anymore, it is now just an alias of pipe. effect(effectGenUsesAdapter)"
196764
+ }]
196765
+ }
196514
196766
  },
196515
196767
  {
196516
- "name": "genericEffectServices",
196517
- "description": "Prevents services with type parameters that cannot be discriminated at runtime",
196768
+ "name": "effectInFailure",
196769
+ "group": "antipattern",
196770
+ "description": "Warns when an Effect is used inside an Effect failure channel",
196518
196771
  "defaultSeverity": "warning",
196519
- "codes": [377043]
196772
+ "fixable": false,
196773
+ "supportedEffect": ["v3", "v4"],
196774
+ "codes": [377054],
196775
+ "preview": {
196776
+ "sourceText": "import { Effect } from \"effect\"\n\nexport const preview = Effect.try({\n try: () => JSON.parse(\"{\"),\n catch: (error) => Effect.logError(error)\n})\n",
196777
+ "diagnostics": [{
196778
+ "start": 46,
196779
+ "end": 53,
196780
+ "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)"
196781
+ }, {
196782
+ "start": 56,
196783
+ "end": 144,
196784
+ "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)"
196785
+ }]
196786
+ }
196787
+ },
196788
+ {
196789
+ "name": "effectInVoidSuccess",
196790
+ "group": "antipattern",
196791
+ "description": "Detects nested Effects in void success channels that may cause unexecuted effects",
196792
+ "defaultSeverity": "warning",
196793
+ "fixable": false,
196794
+ "supportedEffect": ["v3", "v4"],
196795
+ "codes": [377020],
196796
+ "preview": {
196797
+ "sourceText": "import * as Effect from \"effect/Effect\"\n\nexport const preview: Effect.Effect<void> = Effect.succeed(\n Effect.log(\"nested\")\n)\n",
196798
+ "diagnostics": [{
196799
+ "start": 54,
196800
+ "end": 61,
196801
+ "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)"
196802
+ }]
196803
+ }
196520
196804
  },
196521
196805
  {
196522
196806
  "name": "globalErrorInEffectCatch",
196807
+ "group": "antipattern",
196523
196808
  "description": "Warns when catch callbacks return global Error type instead of typed errors",
196524
196809
  "defaultSeverity": "warning",
196525
- "codes": [377022]
196810
+ "fixable": false,
196811
+ "supportedEffect": ["v3", "v4"],
196812
+ "codes": [377022],
196813
+ "preview": {
196814
+ "sourceText": "import { Effect } from \"effect\"\n\nexport const preview = Effect.tryPromise({\n try: async () => 1,\n catch: (error) => new Error(String(error))\n})\n",
196815
+ "diagnostics": [{
196816
+ "start": 56,
196817
+ "end": 73,
196818
+ "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)"
196819
+ }]
196820
+ }
196526
196821
  },
196527
196822
  {
196528
196823
  "name": "globalErrorInEffectFailure",
196824
+ "group": "antipattern",
196529
196825
  "description": "Warns when the global Error type is used in an Effect failure channel",
196530
196826
  "defaultSeverity": "warning",
196531
- "codes": [377023]
196532
- },
196533
- {
196534
- "name": "instanceOfSchema",
196535
- "description": "Suggests using Schema.is instead of instanceof for Effect Schema types",
196536
- "defaultSeverity": "off",
196537
- "codes": [377042]
196827
+ "fixable": false,
196828
+ "supportedEffect": ["v3", "v4"],
196829
+ "codes": [377023],
196830
+ "preview": {
196831
+ "sourceText": "import { Effect } from \"effect\"\n\nexport const preview = Effect.fail(new Error(\"boom\"))\n",
196832
+ "diagnostics": [{
196833
+ "start": 68,
196834
+ "end": 85,
196835
+ "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)"
196836
+ }]
196837
+ }
196538
196838
  },
196539
196839
  {
196540
196840
  "name": "layerMergeAllWithDependencies",
196841
+ "group": "antipattern",
196541
196842
  "description": "Detects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires",
196542
196843
  "defaultSeverity": "warning",
196543
- "codes": [377035]
196844
+ "fixable": true,
196845
+ "supportedEffect": ["v3", "v4"],
196846
+ "codes": [377035],
196847
+ "preview": {
196848
+ "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",
196849
+ "diagnostics": [{
196850
+ "start": 355,
196851
+ "end": 364,
196852
+ "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)"
196853
+ }]
196854
+ }
196544
196855
  },
196545
196856
  {
196546
196857
  "name": "leakingRequirements",
196858
+ "group": "antipattern",
196547
196859
  "description": "Detects implementation services leaked in service methods",
196548
196860
  "defaultSeverity": "suggestion",
196549
- "codes": [377041]
196861
+ "fixable": false,
196862
+ "supportedEffect": ["v3", "v4"],
196863
+ "codes": [377041],
196864
+ "preview": {
196865
+ "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",
196866
+ "diagnostics": [{
196867
+ "start": 212,
196868
+ "end": 217,
196869
+ "text": "Methods of this Service require 'FileSystem' from every caller. This leaks implementation details — resolve these dependencies at Layer creation time. effect(leakingRequirements)"
196870
+ }]
196871
+ }
196550
196872
  },
196551
196873
  {
196552
- "name": "missedPipeableOpportunity",
196553
- "description": "Suggests using .pipe() for nested function calls",
196554
- "defaultSeverity": "off",
196555
- "codes": [377050]
196874
+ "name": "multipleEffectProvide",
196875
+ "group": "antipattern",
196876
+ "description": "Warns against chaining Effect.provide calls which can cause service lifecycle issues",
196877
+ "defaultSeverity": "warning",
196878
+ "fixable": true,
196879
+ "supportedEffect": ["v3", "v4"],
196880
+ "codes": [377033],
196881
+ "preview": {
196882
+ "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",
196883
+ "diagnostics": [{
196884
+ "start": 348,
196885
+ "end": 373,
196886
+ "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)"
196887
+ }]
196888
+ }
196556
196889
  },
196557
196890
  {
196558
- "name": "missingEffectContext",
196559
- "description": "Detects Effect values with unhandled context requirements",
196560
- "defaultSeverity": "error",
196561
- "codes": [377004]
196891
+ "name": "returnEffectInGen",
196892
+ "group": "antipattern",
196893
+ "description": "Warns when returning an Effect in a generator causes nested Effect<Effect<...>>",
196894
+ "defaultSeverity": "suggestion",
196895
+ "fixable": true,
196896
+ "supportedEffect": ["v3", "v4"],
196897
+ "codes": [377014],
196898
+ "preview": {
196899
+ "sourceText": "import * as Effect from \"effect/Effect\"\n\nexport const preview = Effect.gen(function*() {\n return Effect.succeed(1)\n})\n",
196900
+ "diagnostics": [{
196901
+ "start": 91,
196902
+ "end": 97,
196903
+ "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)"
196904
+ }]
196905
+ }
196562
196906
  },
196563
196907
  {
196564
- "name": "missingEffectError",
196565
- "description": "Detects Effect values with unhandled error types",
196566
- "defaultSeverity": "error",
196567
- "codes": [377003]
196908
+ "name": "runEffectInsideEffect",
196909
+ "group": "antipattern",
196910
+ "description": "Suggests using Runtime methods instead of Effect.run* inside Effect contexts",
196911
+ "defaultSeverity": "suggestion",
196912
+ "fixable": true,
196913
+ "supportedEffect": ["v3"],
196914
+ "codes": [377024, 377025],
196915
+ "preview": {
196916
+ "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",
196917
+ "diagnostics": [{
196918
+ "start": 101,
196919
+ "end": 115,
196920
+ "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)"
196921
+ }]
196922
+ }
196568
196923
  },
196569
196924
  {
196570
- "name": "missingEffectServiceDependency",
196571
- "description": "Checks that Effect.Service dependencies satisfy all required layer inputs",
196572
- "defaultSeverity": "off",
196573
- "codes": [377039, 377040]
196925
+ "name": "schemaSyncInEffect",
196926
+ "group": "antipattern",
196927
+ "description": "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
196928
+ "defaultSeverity": "suggestion",
196929
+ "fixable": false,
196930
+ "supportedEffect": ["v3"],
196931
+ "codes": [377037],
196932
+ "preview": {
196933
+ "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",
196934
+ "diagnostics": [{
196935
+ "start": 276,
196936
+ "end": 293,
196937
+ "text": "Using Schema.decodeSync inside an Effect generator is not recommended. Use Schema.decode instead to get properly typed error channel. effect(schemaSyncInEffect)"
196938
+ }]
196939
+ }
196574
196940
  },
196575
196941
  {
196576
- "name": "missingLayerContext",
196577
- "description": "Detects Layer values with unhandled context requirements",
196578
- "defaultSeverity": "error",
196579
- "codes": [377034]
196942
+ "name": "scopeInLayerEffect",
196943
+ "group": "antipattern",
196944
+ "description": "Suggests using Layer.scoped instead of Layer.effect when Scope is in requirements",
196945
+ "defaultSeverity": "warning",
196946
+ "fixable": true,
196947
+ "supportedEffect": ["v3"],
196948
+ "codes": [377031],
196949
+ "preview": {
196950
+ "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",
196951
+ "diagnostics": [{
196952
+ "start": 211,
196953
+ "end": 338,
196954
+ "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)"
196955
+ }]
196956
+ }
196580
196957
  },
196581
196958
  {
196582
- "name": "missingReturnYieldStar",
196583
- "description": "Suggests using return yield* for Effects that never succeed",
196584
- "defaultSeverity": "error",
196585
- "codes": [377006]
196959
+ "name": "strictEffectProvide",
196960
+ "group": "antipattern",
196961
+ "description": "Warns when using Effect.provide with layers outside of application entry points",
196962
+ "defaultSeverity": "off",
196963
+ "fixable": false,
196964
+ "supportedEffect": ["v3", "v4"],
196965
+ "codes": [377032],
196966
+ "preview": {
196967
+ "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",
196968
+ "diagnostics": [{
196969
+ "start": 235,
196970
+ "end": 265,
196971
+ "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)"
196972
+ }]
196973
+ }
196586
196974
  },
196587
196975
  {
196588
- "name": "missingStarInYieldEffectGen",
196589
- "description": "Detects bare yield (without *) inside Effect generator scopes",
196590
- "defaultSeverity": "error",
196591
- "codes": [377007, 377008]
196976
+ "name": "tryCatchInEffectGen",
196977
+ "group": "antipattern",
196978
+ "description": "Discourages try/catch in Effect generators in favor of Effect error handling",
196979
+ "defaultSeverity": "suggestion",
196980
+ "fixable": false,
196981
+ "supportedEffect": ["v3", "v4"],
196982
+ "codes": [377012],
196983
+ "preview": {
196984
+ "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",
196985
+ "diagnostics": [{
196986
+ "start": 91,
196987
+ "end": 151,
196988
+ "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)"
196989
+ }]
196990
+ }
196592
196991
  },
196593
196992
  {
196594
- "name": "multipleEffectProvide",
196595
- "description": "Warns against chaining Effect.provide calls which can cause service lifecycle issues",
196993
+ "name": "unknownInEffectCatch",
196994
+ "group": "antipattern",
196995
+ "description": "Warns when catch callbacks return unknown instead of typed errors",
196596
196996
  "defaultSeverity": "warning",
196597
- "codes": [377033]
196997
+ "fixable": false,
196998
+ "supportedEffect": ["v3", "v4"],
196999
+ "codes": [377021],
197000
+ "preview": {
197001
+ "sourceText": "import { Effect } from \"effect\"\n\nexport const preview = Effect.tryPromise({\n try: async () => 1,\n catch: (error) => error\n})\n",
197002
+ "diagnostics": [{
197003
+ "start": 56,
197004
+ "end": 73,
197005
+ "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)"
197006
+ }]
197007
+ }
196598
197008
  },
196599
197009
  {
196600
- "name": "nodeBuiltinImport",
196601
- "description": "Warns when importing Node.js built-in modules that have Effect-native counterparts",
197010
+ "name": "extendsNativeError",
197011
+ "group": "effectNative",
197012
+ "description": "Warns when a class directly extends the native Error class",
196602
197013
  "defaultSeverity": "off",
196603
- "codes": [377057]
197014
+ "fixable": false,
197015
+ "supportedEffect": ["v3", "v4"],
197016
+ "codes": [377055],
197017
+ "preview": {
197018
+ "sourceText": "\nexport class PreviewError extends Error {}\n",
197019
+ "diagnostics": [{
197020
+ "start": 14,
197021
+ "end": 26,
197022
+ "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)"
197023
+ }]
197024
+ }
196604
197025
  },
196605
197026
  {
196606
- "name": "nonObjectEffectServiceType",
196607
- "description": "Ensures Effect.Service types are objects, not primitives",
196608
- "defaultSeverity": "error",
196609
- "codes": [377048]
197027
+ "name": "globalFetch",
197028
+ "group": "effectNative",
197029
+ "description": "Warns when using the global fetch function instead of the Effect HTTP client",
197030
+ "defaultSeverity": "off",
197031
+ "fixable": false,
197032
+ "supportedEffect": ["v3", "v4"],
197033
+ "codes": [377061],
197034
+ "preview": {
197035
+ "sourceText": "\nimport { Effect } from \"effect\"\n\nvoid Effect\n\nexport const preview = fetch(\"https://example.com\")\n",
197036
+ "diagnostics": [{
197037
+ "start": 70,
197038
+ "end": 75,
197039
+ "text": "Prefer using HttpClient from effect/unstable/http instead of the global 'fetch' function. effect(globalFetch)"
197040
+ }]
197041
+ }
196610
197042
  },
196611
197043
  {
196612
- "name": "outdatedApi",
196613
- "description": "Detects usage of APIs that have been removed or renamed in Effect v4",
196614
- "defaultSeverity": "warning",
196615
- "codes": [377052, 377053]
197044
+ "name": "instanceOfSchema",
197045
+ "group": "effectNative",
197046
+ "description": "Suggests using Schema.is instead of instanceof for Effect Schema types",
197047
+ "defaultSeverity": "off",
197048
+ "fixable": true,
197049
+ "supportedEffect": ["v3", "v4"],
197050
+ "codes": [377042],
197051
+ "preview": {
197052
+ "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",
197053
+ "diagnostics": [{
197054
+ "start": 159,
197055
+ "end": 180,
197056
+ "text": "Consider using Schema.is instead of instanceof for Effect Schema types. effect(instanceOfSchema)"
197057
+ }]
197058
+ }
196616
197059
  },
196617
197060
  {
196618
- "name": "overriddenSchemaConstructor",
196619
- "description": "Prevents overriding constructors in Schema classes which breaks decoding behavior",
196620
- "defaultSeverity": "error",
196621
- "codes": [377044]
197061
+ "name": "nodeBuiltinImport",
197062
+ "group": "effectNative",
197063
+ "description": "Warns when importing Node.js built-in modules that have Effect-native counterparts",
197064
+ "defaultSeverity": "off",
197065
+ "fixable": false,
197066
+ "supportedEffect": ["v3", "v4"],
197067
+ "codes": [377057],
197068
+ "preview": {
197069
+ "sourceText": "import fs from \"node:fs\"\n\nexport const preview = fs.readFileSync\n",
197070
+ "diagnostics": [{
197071
+ "start": 15,
197072
+ "end": 24,
197073
+ "text": "Prefer using FileSystem from @effect/platform instead of the Node.js 'fs' module. effect(nodeBuiltinImport)"
197074
+ }]
197075
+ }
196622
197076
  },
196623
197077
  {
196624
197078
  "name": "preferSchemaOverJson",
197079
+ "group": "effectNative",
196625
197080
  "description": "Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify",
196626
197081
  "defaultSeverity": "suggestion",
196627
- "codes": [377026]
197082
+ "fixable": false,
197083
+ "supportedEffect": ["v3", "v4"],
197084
+ "codes": [377026],
197085
+ "preview": {
197086
+ "sourceText": "import { Effect } from \"effect\"\n\nexport const preview = Effect.gen(function*() {\n const text = yield* Effect.succeed('{\"ok\":true}')\n return JSON.parse(text)\n})\n",
197087
+ "diagnostics": [{
197088
+ "start": 142,
197089
+ "end": 158,
197090
+ "text": "Consider using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify. effect(preferSchemaOverJson)"
197091
+ }]
197092
+ }
196628
197093
  },
196629
197094
  {
196630
- "name": "redundantSchemaTagIdentifier",
196631
- "description": "Suggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest",
197095
+ "name": "catchAllToMapError",
197096
+ "group": "style",
197097
+ "description": "Suggests using Effect.mapError instead of Effect.catch + Effect.fail",
196632
197098
  "defaultSeverity": "suggestion",
196633
- "codes": [377045]
197099
+ "fixable": true,
197100
+ "supportedEffect": ["v3", "v4"],
197101
+ "codes": [377010],
197102
+ "preview": {
197103
+ "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",
197104
+ "diagnostics": [{
197105
+ "start": 150,
197106
+ "end": 162,
197107
+ "text": "You can use Effect.mapError instead of Effect.catch + Effect.fail to transform the error type. effect(catchAllToMapError)"
197108
+ }]
197109
+ }
196634
197110
  },
196635
197111
  {
196636
- "name": "returnEffectInGen",
196637
- "description": "Warns when returning an Effect in a generator causes nested Effect<Effect<...>>",
196638
- "defaultSeverity": "suggestion",
196639
- "codes": [377014]
197112
+ "name": "deterministicKeys",
197113
+ "group": "style",
197114
+ "description": "Enforces deterministic naming for service/tag/error identifiers based on class names",
197115
+ "defaultSeverity": "off",
197116
+ "fixable": true,
197117
+ "supportedEffect": ["v3", "v4"],
197118
+ "codes": [377049],
197119
+ "preview": {
197120
+ "sourceText": "import { ServiceMap } from \"effect\"\n\nexport class RenamedService\n extends ServiceMap.Service<RenamedService, {}>()(\"CustomIdentifier\") {}\n",
197121
+ "diagnostics": []
197122
+ }
196640
197123
  },
196641
197124
  {
196642
- "name": "runEffectInsideEffect",
196643
- "description": "Suggests using Runtime methods instead of Effect.run* inside Effect contexts",
197125
+ "name": "effectFnOpportunity",
197126
+ "group": "style",
197127
+ "description": "Suggests using Effect.fn for functions that return an Effect",
196644
197128
  "defaultSeverity": "suggestion",
196645
- "codes": [377024, 377025]
197129
+ "fixable": true,
197130
+ "supportedEffect": ["v3", "v4"],
197131
+ "codes": [377047],
197132
+ "preview": {
197133
+ "sourceText": "import * as Effect from \"effect/Effect\"\n\nexport const preview = () => Effect.gen(function*() {\n return yield* Effect.succeed(1)\n})\n",
197134
+ "diagnostics": [{
197135
+ "start": 54,
197136
+ "end": 61,
197137
+ "text": "Can be rewritten as a reusable function: Effect.fn(function*() { ... }). effect(effectFnOpportunity)"
197138
+ }]
197139
+ }
196646
197140
  },
196647
197141
  {
196648
- "name": "schemaStructWithTag",
196649
- "description": "Suggests using Schema.TaggedStruct instead of Schema.Struct with _tag field",
197142
+ "name": "effectMapVoid",
197143
+ "group": "style",
197144
+ "description": "Suggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {})",
196650
197145
  "defaultSeverity": "suggestion",
196651
- "codes": [377036]
197146
+ "fixable": true,
197147
+ "supportedEffect": ["v3", "v4"],
197148
+ "codes": [377018],
197149
+ "preview": {
197150
+ "sourceText": "import { Effect } from \"effect\"\n\nexport const preview = Effect.succeed(1).pipe(\n Effect.map(() => undefined)\n)\n",
197151
+ "diagnostics": [{
197152
+ "start": 82,
197153
+ "end": 92,
197154
+ "text": "Effect.asVoid can be used instead to discard the success value. effect(effectMapVoid)"
197155
+ }]
197156
+ }
196652
197157
  },
196653
197158
  {
196654
- "name": "schemaSyncInEffect",
196655
- "description": "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
197159
+ "name": "effectSucceedWithVoid",
197160
+ "group": "style",
197161
+ "description": "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
196656
197162
  "defaultSeverity": "suggestion",
196657
- "codes": [377037]
197163
+ "fixable": true,
197164
+ "supportedEffect": ["v3", "v4"],
197165
+ "codes": [377016],
197166
+ "preview": {
197167
+ "sourceText": "import { Effect } from \"effect\"\n\nexport const preview = Effect.succeed(undefined)\n",
197168
+ "diagnostics": [{
197169
+ "start": 56,
197170
+ "end": 81,
197171
+ "text": "Effect.void can be used instead of Effect.succeed(undefined) or Effect.succeed(void 0). effect(effectSucceedWithVoid)"
197172
+ }]
197173
+ }
196658
197174
  },
196659
197175
  {
196660
- "name": "schemaUnionOfLiterals",
196661
- "description": "Suggests combining multiple Schema.Literal calls in Schema.Union into a single Schema.Literal",
197176
+ "name": "missedPipeableOpportunity",
197177
+ "group": "style",
197178
+ "description": "Suggests using .pipe() for nested function calls",
196662
197179
  "defaultSeverity": "off",
196663
- "codes": [377038]
197180
+ "fixable": true,
197181
+ "supportedEffect": ["v3", "v4"],
197182
+ "codes": [377050],
197183
+ "preview": {
197184
+ "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",
197185
+ "diagnostics": [{
197186
+ "start": 116,
197187
+ "end": 172,
197188
+ "text": "Nested function calls can be converted to pipeable style for better readability; consider using Schema.decodeEffect(User)({ id: 1 }).pipe(...) instead. effect(missedPipeableOpportunity)"
197189
+ }]
197190
+ }
196664
197191
  },
196665
197192
  {
196666
- "name": "scopeInLayerEffect",
196667
- "description": "Suggests using Layer.scoped instead of Layer.effect when Scope is in requirements",
196668
- "defaultSeverity": "warning",
196669
- "codes": [377031]
197193
+ "name": "missingEffectServiceDependency",
197194
+ "group": "style",
197195
+ "description": "Checks that Effect.Service dependencies satisfy all required layer inputs",
197196
+ "defaultSeverity": "off",
197197
+ "fixable": false,
197198
+ "supportedEffect": ["v3"],
197199
+ "codes": [377039, 377040],
197200
+ "preview": {
197201
+ "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",
197202
+ "diagnostics": [{
197203
+ "start": 128,
197204
+ "end": 132,
197205
+ "text": "Service 'Db' is required but not provided by dependencies. effect(missingEffectServiceDependency)"
197206
+ }]
197207
+ }
196670
197208
  },
196671
197209
  {
196672
- "name": "serviceNotAsClass",
196673
- "description": "Warns when ServiceMap.Service is used as a variable instead of a class declaration",
196674
- "defaultSeverity": "off",
196675
- "codes": [377056]
197210
+ "name": "redundantSchemaTagIdentifier",
197211
+ "group": "style",
197212
+ "description": "Suggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest",
197213
+ "defaultSeverity": "suggestion",
197214
+ "fixable": true,
197215
+ "supportedEffect": ["v3", "v4"],
197216
+ "codes": [377045],
197217
+ "preview": {
197218
+ "sourceText": "import * as Schema from \"effect/Schema\"\n\nexport class Preview\n extends Schema.TaggedClass<Preview>(\"Preview\")(\"Preview\", {\n value: Schema.String\n }) {}\n",
197219
+ "diagnostics": [{
197220
+ "start": 100,
197221
+ "end": 109,
197222
+ "text": "Identifier 'Preview' is redundant since it equals the _tag value. effect(redundantSchemaTagIdentifier)"
197223
+ }]
197224
+ }
196676
197225
  },
196677
197226
  {
196678
- "name": "strictBooleanExpressions",
196679
- "description": "Enforces boolean types in conditional expressions for type safety",
196680
- "defaultSeverity": "off",
196681
- "codes": [377029]
197227
+ "name": "schemaStructWithTag",
197228
+ "group": "style",
197229
+ "description": "Suggests using Schema.TaggedStruct instead of Schema.Struct with _tag field",
197230
+ "defaultSeverity": "suggestion",
197231
+ "fixable": true,
197232
+ "supportedEffect": ["v3", "v4"],
197233
+ "codes": [377036],
197234
+ "preview": {
197235
+ "sourceText": "import * as Schema from \"effect/Schema\"\n\nexport const preview = Schema.Struct({\n _tag: Schema.Literal(\"User\"),\n name: Schema.String\n})\n",
197236
+ "diagnostics": [{
197237
+ "start": 64,
197238
+ "end": 136,
197239
+ "text": "Schema.Struct with a _tag field can be simplified to Schema.TaggedStruct to make the tag optional in the constructor. effect(schemaStructWithTag)"
197240
+ }]
197241
+ }
196682
197242
  },
196683
197243
  {
196684
- "name": "strictEffectProvide",
196685
- "description": "Warns when using Effect.provide with layers outside of application entry points",
197244
+ "name": "schemaUnionOfLiterals",
197245
+ "group": "style",
197246
+ "description": "Suggests combining multiple Schema.Literal calls in Schema.Union into a single Schema.Literal",
196686
197247
  "defaultSeverity": "off",
196687
- "codes": [377032]
197248
+ "fixable": true,
197249
+ "supportedEffect": ["v3"],
197250
+ "codes": [377038],
197251
+ "preview": {
197252
+ "sourceText": "import * as Schema from \"effect/Schema\"\n\nexport const preview = Schema.Union(Schema.Literal(\"a\"), Schema.Literal(\"b\"))\n",
197253
+ "diagnostics": [{
197254
+ "start": 64,
197255
+ "end": 118,
197256
+ "text": "A Schema.Union of multiple Schema.Literal calls can be simplified to a single Schema.Literal call. effect(schemaUnionOfLiterals)"
197257
+ }]
197258
+ }
196688
197259
  },
196689
197260
  {
196690
- "name": "tryCatchInEffectGen",
196691
- "description": "Discourages try/catch in Effect generators in favor of Effect error handling",
196692
- "defaultSeverity": "suggestion",
196693
- "codes": [377012]
197261
+ "name": "serviceNotAsClass",
197262
+ "group": "style",
197263
+ "description": "Warns when ServiceMap.Service is used as a variable instead of a class declaration",
197264
+ "defaultSeverity": "off",
197265
+ "fixable": true,
197266
+ "supportedEffect": ["v4"],
197267
+ "codes": [377056],
197268
+ "preview": {
197269
+ "sourceText": "import { ServiceMap } from \"effect\"\n\nexport const Preview = ServiceMap.Service<{ port: number }>(\"Preview\")\n",
197270
+ "diagnostics": [{
197271
+ "start": 60,
197272
+ "end": 107,
197273
+ "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)"
197274
+ }]
197275
+ }
196694
197276
  },
196695
197277
  {
196696
- "name": "unknownInEffectCatch",
196697
- "description": "Warns when catch callbacks return unknown instead of typed errors",
196698
- "defaultSeverity": "warning",
196699
- "codes": [377021]
197278
+ "name": "strictBooleanExpressions",
197279
+ "group": "style",
197280
+ "description": "Enforces boolean types in conditional expressions for type safety",
197281
+ "defaultSeverity": "off",
197282
+ "fixable": false,
197283
+ "supportedEffect": ["v3", "v4"],
197284
+ "codes": [377029],
197285
+ "preview": {
197286
+ "sourceText": "\ndeclare const value: string | undefined\nexport const preview = value ? 1 : 0\n",
197287
+ "diagnostics": [{
197288
+ "start": 64,
197289
+ "end": 69,
197290
+ "text": "Unexpected `string` type in condition, expected strictly a boolean instead. effect(strictBooleanExpressions)"
197291
+ }, {
197292
+ "start": 64,
197293
+ "end": 69,
197294
+ "text": "Unexpected `undefined` type in condition, expected strictly a boolean instead. effect(strictBooleanExpressions)"
197295
+ }]
197296
+ }
196700
197297
  },
196701
197298
  {
196702
197299
  "name": "unnecessaryEffectGen",
197300
+ "group": "style",
196703
197301
  "description": "Suggests removing Effect.gen when it contains only a single return statement",
196704
197302
  "defaultSeverity": "suggestion",
196705
- "codes": [377017]
197303
+ "fixable": true,
197304
+ "supportedEffect": ["v3", "v4"],
197305
+ "codes": [377017],
197306
+ "preview": {
197307
+ "sourceText": "import * as Effect from \"effect/Effect\"\n\nexport const preview = Effect.gen(function*() {\n return yield* Effect.succeed(1)\n})\n",
197308
+ "diagnostics": [{
197309
+ "start": 64,
197310
+ "end": 125,
197311
+ "text": "This Effect.gen contains a single return statement. effect(unnecessaryEffectGen)"
197312
+ }]
197313
+ }
196706
197314
  },
196707
197315
  {
196708
197316
  "name": "unnecessaryFailYieldableError",
197317
+ "group": "style",
196709
197318
  "description": "Suggests yielding yieldable errors directly instead of wrapping with Effect.fail",
196710
197319
  "defaultSeverity": "suggestion",
196711
- "codes": [377019]
197320
+ "fixable": true,
197321
+ "supportedEffect": ["v3", "v4"],
197322
+ "codes": [377019],
197323
+ "preview": {
197324
+ "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",
197325
+ "diagnostics": [{
197326
+ "start": 178,
197327
+ "end": 183,
197328
+ "text": "This Effect.fail call uses a yieldable error type as argument. You can yield* the error directly instead. effect(unnecessaryFailYieldableError)"
197329
+ }]
197330
+ }
196712
197331
  },
196713
197332
  {
196714
197333
  "name": "unnecessaryPipe",
197334
+ "group": "style",
196715
197335
  "description": "Removes pipe calls with no arguments",
196716
197336
  "defaultSeverity": "suggestion",
196717
- "codes": [377013]
197337
+ "fixable": true,
197338
+ "supportedEffect": ["v3", "v4"],
197339
+ "codes": [377013],
197340
+ "preview": {
197341
+ "sourceText": "import { pipe } from \"effect/Function\"\n\nexport const preview = pipe(1)\n",
197342
+ "diagnostics": [{
197343
+ "start": 63,
197344
+ "end": 70,
197345
+ "text": "This pipe call contains no arguments. effect(unnecessaryPipe)"
197346
+ }]
197347
+ }
196718
197348
  },
196719
197349
  {
196720
197350
  "name": "unnecessaryPipeChain",
197351
+ "group": "style",
196721
197352
  "description": "Simplifies chained pipe calls into a single pipe call",
196722
197353
  "defaultSeverity": "suggestion",
196723
- "codes": [377015]
197354
+ "fixable": true,
197355
+ "supportedEffect": ["v3", "v4"],
197356
+ "codes": [377015],
197357
+ "preview": {
197358
+ "sourceText": "import * as Effect from \"effect/Effect\"\n\nexport const preview = Effect.succeed(1).pipe(Effect.asVoid).pipe(Effect.as(\"done\"))\n",
197359
+ "diagnostics": [{
197360
+ "start": 64,
197361
+ "end": 125,
197362
+ "text": "Chained pipe calls can be simplified to a single pipe call. effect(unnecessaryPipeChain)"
197363
+ }]
197364
+ }
196724
197365
  }
196725
197366
  ];
196726
197367
 
196727
197368
  //#endregion
196728
197369
  //#region src/setup/rule-info.ts
196729
197370
  function getAllRules() {
196730
- return rules_default;
197371
+ return rules;
197372
+ }
197373
+ function getAllGroups() {
197374
+ return groups;
196731
197375
  }
196732
197376
  function cycleSeverity(current, direction) {
196733
197377
  const order = [
@@ -196758,187 +197402,349 @@ function getSeverityShortName(severity) {
196758
197402
  const RESET = "\x1B[0m";
196759
197403
  const BOLD = "\x1B[0;1m";
196760
197404
  const DIM = "\x1B[0;90m";
196761
- const GREEN = "\x1B[0;32m";
196762
- const WHITE = "\x1B[0;37m";
196763
- const CYAN_BRIGHT = "\x1B[0;96m";
196764
- const BG_BLACK_BRIGHT = "\x1B[0;100m";
196765
- const BG_RED = "\x1B[41m";
196766
- const BG_YELLOW = "\x1B[43m";
196767
- const BG_BLUE = "\x1B[0;44m";
196768
- const BG_CYAN = "\x1B[0;46m";
197405
+ const RED = "\x1B[0;31m";
197406
+ const YELLOW = "\x1B[0;33m";
197407
+ const BLUE = "\x1B[0;34m";
197408
+ const ITALIC = "\x1B[0;3m";
197409
+ const UNDERLINE = "\x1B[0;4m";
196769
197410
  const ansi = (text, code) => `${code}${text}${RESET}`;
196770
197411
  const ERASE_LINE = "\x1B[2K";
196771
197412
  const CURSOR_LEFT = "\r";
196772
197413
  const CURSOR_HIDE = "\x1B[?25l";
196773
197414
  const CURSOR_TO_0 = "\x1B[G";
196774
197415
  const BEEP = "\x07";
197416
+ const ANSI_ESCAPE_REGEX = new RegExp(String.raw`\u001b\[[0-?]*[ -/]*[@-~]`, "g");
197417
+ const stripAnsi = (text) => text.replace(ANSI_ESCAPE_REGEX, "");
197418
+ const visibleLength = (text) => stripAnsi(text).length;
196775
197419
 
196776
197420
  //#endregion
196777
197421
  //#region src/setup/rule-prompt.ts
196778
- function eraseLines(count) {
196779
- let result = "";
196780
- for (let i = 0; i < count; i++) {
196781
- if (i > 0) result += "\x1B[1A";
196782
- result += ERASE_LINE;
196783
- }
196784
- if (count > 0) result += CURSOR_LEFT;
196785
- return result;
196786
- }
197422
+ const diagnosticGroups = getAllGroups();
196787
197423
  const Action = taggedEnum();
196788
- const NEWLINE_REGEX = /\r?\n/;
196789
- function eraseText(text, columns) {
196790
- if (columns === 0) return ERASE_LINE + CURSOR_TO_0;
196791
- let rows = 0;
196792
- const lines = text.split(/\r?\n/);
196793
- for (const line of lines) rows += 1 + Math.floor(Math.max(line.length - 1, 0) / columns);
196794
- return eraseLines(rows);
197424
+ const SEARCH_ICON = "/";
197425
+ const MIN_PREVIEW_AND_MESSAGES_LINES = 18;
197426
+ function getControlsLegend(searchText) {
197427
+ return `\u2190/\u2192 change rule \u2191/\u2193 change severity ${searchText.length === 0 ? `${SEARCH_ICON} type to search` : `${SEARCH_ICON} searching: ${searchText}`}`;
196795
197428
  }
196796
- function entriesToDisplay(cursor, total, maxVisible) {
196797
- const max = maxVisible === void 0 ? total : maxVisible;
196798
- let startIndex = Math.min(total - max, cursor - Math.floor(max / 2));
196799
- if (startIndex < 0) startIndex = 0;
196800
- const endIndex = Math.min(startIndex + max, total);
197429
+ function getSeveritySymbol(severity) {
196801
197430
  return {
196802
- startIndex,
196803
- endIndex
196804
- };
197431
+ off: ".",
197432
+ suggestion: "?",
197433
+ message: "i",
197434
+ warning: "!",
197435
+ error: "x"
197436
+ }[severity];
196805
197437
  }
196806
- const figuresValue = {
196807
- arrowUp: "↑",
196808
- arrowDown: "↓",
196809
- tick: "✔",
196810
- pointerSmall: "›"
196811
- };
196812
197438
  function getSeverityStyle(severity) {
196813
197439
  return {
196814
- off: WHITE + BG_BLACK_BRIGHT,
196815
- suggestion: WHITE + BG_CYAN,
196816
- message: WHITE + BG_BLUE,
196817
- warning: WHITE + BG_YELLOW,
196818
- error: WHITE + BG_RED
197440
+ off: DIM,
197441
+ suggestion: DIM,
197442
+ message: BLUE,
197443
+ warning: YELLOW,
197444
+ error: RED
196819
197445
  }[severity];
196820
197446
  }
196821
- function renderOutput(leadingSymbol, trailingSymbol, options) {
196822
- const annotateLine = (line) => ansi(line, BOLD);
196823
- const prefix = leadingSymbol + " ";
196824
- return match$6(options.message.split(NEWLINE_REGEX), {
196825
- onEmpty: () => `${prefix}${trailingSymbol}`,
196826
- onNonEmpty: (promptLines) => {
196827
- return `${prefix}${map$7(promptLines, (line) => annotateLine(line)).join("\n ")} ${trailingSymbol} `;
197447
+ function renderEntry(entry, severity, isSelected) {
197448
+ return `${ansi(getSeveritySymbol(severity), getSeverityStyle(severity))} ${isSelected ? ansi(entry.name, UNDERLINE) : entry.name}`;
197449
+ }
197450
+ function rowsForLength(length, columns) {
197451
+ if (columns <= 0) return 1;
197452
+ return Math.max(1, 1 + Math.floor(Math.max(length - 1, 0) / columns));
197453
+ }
197454
+ function eraseRenderedLines(lines, columns) {
197455
+ let result = "";
197456
+ for (let lineIndex = lines.length - 1; lineIndex >= 0; lineIndex--) {
197457
+ const rows = rowsForLength(visibleLength(lines[lineIndex] ?? ""), columns);
197458
+ for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
197459
+ result += ERASE_LINE;
197460
+ if (!(lineIndex === 0 && rowIndex === rows - 1)) result += "\x1B[1A";
196828
197461
  }
196829
- });
197462
+ }
197463
+ if (lines.length > 0) result += CURSOR_LEFT;
197464
+ return result;
196830
197465
  }
196831
- function renderRules(state, options, figs, columns) {
196832
- const toDisplay = entriesToDisplay(state.index, options.rules.length, options.maxPerPage);
196833
- const documents = [];
196834
- for (let index = toDisplay.startIndex; index < toDisplay.endIndex; index++) {
196835
- const rule = options.rules[index];
196836
- const isHighlighted = state.index === index;
196837
- const currentSeverity = state.severities[rule.name] ?? rule.defaultSeverity;
196838
- const hasChanged = currentSeverity !== rule.defaultSeverity;
196839
- let prefix = " ";
196840
- if (index === toDisplay.startIndex && toDisplay.startIndex > 0) prefix = figs.arrowUp;
196841
- else if (index === toDisplay.endIndex - 1 && toDisplay.endIndex < options.rules.length) prefix = figs.arrowDown;
196842
- const severityStr = ansi(` ${getSeverityShortName(currentSeverity).padEnd(MAX_SEVERITY_LENGTH, " ")} `, getSeverityStyle(currentSeverity));
196843
- const nameText = hasChanged ? `${rule.name}*` : rule.name;
196844
- const nameStr = isHighlighted ? ansi(nameText, CYAN_BRIGHT) : nameText;
196845
- const mainLine = `${prefix} ${severityStr} ${nameStr}`;
196846
- if (isHighlighted && rule.description) {
196847
- const indentWidth = 2 + (MAX_SEVERITY_LENGTH + 2) + 1;
196848
- const indent = " ".repeat(indentWidth);
196849
- const availableWidth = columns - indentWidth;
196850
- const truncatedDescription = availableWidth > 0 && rule.description.length > availableWidth ? rule.description.substring(0, availableWidth - 1) + "…" : rule.description;
196851
- documents.push(mainLine + "\n" + ansi(indent + truncatedDescription, DIM));
196852
- } else documents.push(mainLine);
197466
+ function wrapPaddedText(text, initialPadding, endPadding, columns) {
197467
+ if (text.length === 0) return [initialPadding + endPadding];
197468
+ const available = Math.max(columns - visibleLength(initialPadding) - visibleLength(endPadding), 1);
197469
+ const lines = [];
197470
+ let remaining = text;
197471
+ while (remaining.length > 0) {
197472
+ const chunk = remaining.slice(0, available);
197473
+ lines.push(initialPadding + chunk + endPadding);
197474
+ remaining = remaining.slice(chunk.length);
196853
197475
  }
196854
- return documents.join("\n");
197476
+ return lines;
196855
197477
  }
196856
- function renderNextFrame(state, options) {
196857
- return gen(function* () {
196858
- const rulesStr = renderRules(state, options, figuresValue, yield* (yield* Terminal).columns);
196859
- const promptMsg = renderOutput(ansi("?", CYAN_BRIGHT), figuresValue.pointerSmall, options);
196860
- const helpText = ansi("Use ↑/↓ to navigate, ←/→ to change severity, Enter to finish", DIM);
196861
- return CURSOR_HIDE + promptMsg + "\n" + helpText + "\n" + rulesStr;
197478
+ function wrapListItemText(text, firstLinePadding, continuationPadding, endPadding, columns) {
197479
+ if (text.length === 0) return [firstLinePadding + endPadding];
197480
+ const firstAvailable = Math.max(columns - visibleLength(firstLinePadding) - visibleLength(endPadding), 1);
197481
+ const continuationAvailable = Math.max(columns - visibleLength(continuationPadding) - visibleLength(endPadding), 1);
197482
+ const lines = [];
197483
+ let remaining = text;
197484
+ let isFirstLine = true;
197485
+ while (remaining.length > 0) {
197486
+ const padding = isFirstLine ? firstLinePadding : continuationPadding;
197487
+ const available = isFirstLine ? firstAvailable : continuationAvailable;
197488
+ const chunk = remaining.slice(0, available);
197489
+ lines.push(padding + chunk + endPadding);
197490
+ remaining = remaining.slice(chunk.length);
197491
+ isFirstLine = false;
197492
+ }
197493
+ return lines;
197494
+ }
197495
+ function renderPaddedLine(text, initialPadding, endPadding, columns) {
197496
+ const available = Math.max(columns - visibleLength(initialPadding) - visibleLength(endPadding), 0);
197497
+ const truncated = visibleLength(text) <= available ? text : text.slice(0, available);
197498
+ const padding = Math.max(available - visibleLength(truncated), 0);
197499
+ return initialPadding + truncated + " ".repeat(padding) + endPadding;
197500
+ }
197501
+ function mergeHighlightRanges(ranges) {
197502
+ const sorted = ranges.filter((range) => range.end > range.start).slice().sort((a, b) => a.start - b.start);
197503
+ const merged = [];
197504
+ for (const range of sorted) {
197505
+ const previous = merged[merged.length - 1];
197506
+ if (!previous || range.start > previous.end) {
197507
+ merged.push(range);
197508
+ continue;
197509
+ }
197510
+ merged[merged.length - 1] = {
197511
+ start: previous.start,
197512
+ end: Math.max(previous.end, range.end)
197513
+ };
197514
+ }
197515
+ return merged;
197516
+ }
197517
+ function stylePreviewLine(line, lineStart, ranges) {
197518
+ if (line.length === 0) return "";
197519
+ const lineEnd = lineStart + line.length;
197520
+ let cursor = 0;
197521
+ let rendered = "";
197522
+ for (const range of ranges) {
197523
+ const start = Math.max(range.start, lineStart);
197524
+ const end = Math.min(range.end, lineEnd);
197525
+ if (end <= start) continue;
197526
+ const startIndex = start - lineStart;
197527
+ const endIndex = end - lineStart;
197528
+ if (cursor < startIndex) rendered += ansi(line.slice(cursor, startIndex), DIM);
197529
+ rendered += ansi(line.slice(startIndex, endIndex), UNDERLINE);
197530
+ cursor = endIndex;
197531
+ }
197532
+ if (cursor < line.length) rendered += ansi(line.slice(cursor), DIM);
197533
+ return rendered;
197534
+ }
197535
+ function wrapSourceLine(line, lineStart, availableWidth) {
197536
+ if (line.length === 0) return [{
197537
+ text: "",
197538
+ start: lineStart,
197539
+ end: lineStart
197540
+ }];
197541
+ const width = Math.max(availableWidth, 1);
197542
+ const wrapped = [];
197543
+ let offset = 0;
197544
+ while (offset < line.length) {
197545
+ const text = line.slice(offset, offset + width);
197546
+ wrapped.push({
197547
+ text,
197548
+ start: lineStart + offset,
197549
+ end: lineStart + offset + text.length
197550
+ });
197551
+ offset += text.length;
197552
+ }
197553
+ return wrapped;
197554
+ }
197555
+ function wrapPreviewSourceText(sourceText, columns) {
197556
+ const availableWidth = Math.max(columns - 2, 1);
197557
+ const logicalLines = sourceText.split("\n");
197558
+ const wrapped = [];
197559
+ let offset = 0;
197560
+ for (const line of logicalLines) {
197561
+ for (const wrappedLine of wrapSourceLine(line, offset, availableWidth)) wrapped.push(wrappedLine);
197562
+ offset += line.length + 1;
197563
+ }
197564
+ return wrapped;
197565
+ }
197566
+ function renderPreviewSourceText(sourceText, diagnostics, columns) {
197567
+ const ranges = mergeHighlightRanges(diagnostics.map((diagnostic) => ({
197568
+ start: diagnostic.start,
197569
+ end: diagnostic.end
197570
+ })));
197571
+ return wrapPreviewSourceText(sourceText, columns).map((line) => {
197572
+ return CURSOR_TO_0 + renderPaddedLine(stylePreviewLine(line.text, line.start, ranges), " ", "", columns);
196862
197573
  });
196863
197574
  }
196864
- function renderSubmission(state, options) {
196865
- return gen(function* () {
196866
- const changedCount = Object.entries(state.severities).filter(([name, severity]) => {
196867
- const rule = options.rules.find((current) => current.name === name);
196868
- return rule && severity !== rule.defaultSeverity;
196869
- }).length;
196870
- const result = ansi(`${changedCount} rule${changedCount === 1 ? "" : "s"} configured`, WHITE);
196871
- return renderOutput(ansi(figuresValue.tick, GREEN), "", options) + " " + result + "\n";
197575
+ function renderPreviewMessages(diagnostics, columns) {
197576
+ return Array.from(new Set(diagnostics.map((diagnostic) => diagnostic.text))).flatMap((message) => {
197577
+ let isFirstBlock = true;
197578
+ return message.split("\n").flatMap((line) => {
197579
+ const wrappedLines = wrapListItemText(line, isFirstBlock ? "- " : " ", " ", "", columns);
197580
+ isFirstBlock = false;
197581
+ return wrappedLines.map((wrappedLine) => CURSOR_TO_0 + ansi(wrappedLine, DIM + ITALIC));
197582
+ });
196872
197583
  });
196873
197584
  }
196874
- function processCursorUp(state, totalCount) {
196875
- const newIndex = state.index === 0 ? totalCount - 1 : state.index - 1;
196876
- return succeed(Action.NextFrame({ state: {
196877
- ...state,
196878
- index: newIndex
196879
- } }));
197585
+ function renderDivider(columns, legendText) {
197586
+ if (legendText === void 0) return ansi("─".repeat(Math.max(columns, 0)), DIM);
197587
+ const legend = ` ${legendText} `;
197588
+ const legendLength = visibleLength(legend);
197589
+ if (columns <= legendLength) return ansi(legend.slice(0, Math.max(columns, 0)), DIM);
197590
+ const remaining = columns - legendLength;
197591
+ const left = "─".repeat(Math.floor(remaining / 2));
197592
+ const right = "─".repeat(remaining - left.length);
197593
+ return ansi(left + legend + right, DIM);
196880
197594
  }
196881
- function processCursorDown(state, totalCount) {
196882
- const newIndex = (state.index + 1) % totalCount;
196883
- return succeed(Action.NextFrame({ state: {
196884
- ...state,
196885
- index: newIndex
196886
- } }));
197595
+ function renderSelectedRuleDivider(selected, severities, columns) {
197596
+ if (!selected) return renderDivider(columns);
197597
+ const currentSeverity = severities[selected.name] ?? selected.defaultSeverity;
197598
+ return renderDivider(columns, `${selected.name} (currently set as ${getSeverityShortName(currentSeverity)})`);
196887
197599
  }
196888
- function processSeverityChange(state, options, direction) {
196889
- const rule = options.rules[state.index];
196890
- const newSeverity = cycleSeverity(state.severities[rule.name] ?? rule.defaultSeverity, direction);
196891
- return succeed(Action.NextFrame({ state: {
196892
- ...state,
196893
- severities: {
196894
- ...state.severities,
196895
- [rule.name]: newSeverity
196896
- }
196897
- } }));
197600
+ function matchesSearch(entry, searchText) {
197601
+ if (searchText.length === 0) return true;
197602
+ const normalized = searchText.toLowerCase();
197603
+ return entry.name.toLowerCase().includes(normalized) || entry.description.toLowerCase().includes(normalized) || entry.previewSourceText.toLowerCase().includes(normalized);
197604
+ }
197605
+ function getFilteredEntries(entries, searchText) {
197606
+ return entries.filter((entry) => matchesSearch(entry, searchText));
197607
+ }
197608
+ function normalizeStartIndex(length, startIndex) {
197609
+ if (length <= 0) return 0;
197610
+ return (startIndex % length + length) % length;
197611
+ }
197612
+ function isPrintableInput(input) {
197613
+ const printablePattern = new RegExp(String.raw`^[^\u0000-\u001F\u007F]+$`, "u");
197614
+ return !input.key.ctrl && !input.key.meta && input.input !== void 0 && input.input.length > 0 && printablePattern.test(input.input);
197615
+ }
197616
+ function buildVisibleEntries(entries, severities, startIndex, columns) {
197617
+ if (entries.length === 0) return {
197618
+ fullLine: renderPaddedLine(ansi("No matching rules", DIM), " ", " ", columns),
197619
+ visibleRules: []
197620
+ };
197621
+ const itemColumns = Math.max(columns - 4, 0);
197622
+ const separator = " ";
197623
+ const visibleEntries = [];
197624
+ const visibleRules = [];
197625
+ let currentLength = 0;
197626
+ let seenCount = 0;
197627
+ while (seenCount < entries.length) {
197628
+ const rule = entries[(startIndex + seenCount) % entries.length];
197629
+ const entry = renderEntry(rule, severities[rule.name] ?? rule.defaultSeverity, seenCount === 0);
197630
+ const nextLength = visibleEntries.length === 0 ? visibleLength(entry) : currentLength + 2 + visibleLength(entry);
197631
+ if (nextLength > itemColumns) break;
197632
+ visibleEntries.push(entry);
197633
+ visibleRules.push(rule);
197634
+ currentLength = nextLength;
197635
+ seenCount++;
197636
+ }
197637
+ const leftMarker = entries.length > 1 ? ansi("←", DIM) : " ";
197638
+ const rightMarker = entries.length > 1 ? ansi("→", DIM) : " ";
197639
+ return {
197640
+ fullLine: renderPaddedLine(visibleEntries.join(separator), `${leftMarker} `, ` ${rightMarker}`, columns),
197641
+ visibleRules
197642
+ };
196898
197643
  }
196899
- function handleProcess(options) {
197644
+ function buildGroupLine(selected, groups, columns) {
197645
+ if (!selected) return renderPaddedLine("", " ", " ", columns);
197646
+ const selectedIndex = groups.findIndex((group) => group.id === selected.group);
197647
+ return renderPaddedLine(groups.map((_, index) => groups[(selectedIndex + index) % groups.length]).map((group, index) => {
197648
+ const label = group.name.toUpperCase();
197649
+ return index === 0 ? ansi(label, BOLD) : ansi(label, DIM);
197650
+ }).join(" "), " ", " ", columns);
197651
+ }
197652
+ function buildFrame(entries, groups, searchText, severities, startIndex, columns) {
197653
+ const visible = buildVisibleEntries(entries, severities, startIndex, columns);
197654
+ const selected = visible.visibleRules[0];
197655
+ const wrappedDescription = wrapPaddedText(selected?.description ?? "", " ", "", columns);
197656
+ const previewLines = renderPreviewSourceText(selected?.previewSourceText ?? "", selected?.previewDiagnostics ?? [], columns);
197657
+ const previewMessages = renderPreviewMessages(selected?.previewDiagnostics ?? [], columns);
197658
+ const previewSectionLines = [...previewLines, ...previewMessages];
197659
+ const paddingLines = Array.from({ length: Math.max(MIN_PREVIEW_AND_MESSAGES_LINES - previewSectionLines.length, 0) }, () => "");
197660
+ return [
197661
+ CURSOR_HIDE + renderSelectedRuleDivider(selected, severities, columns),
197662
+ ...previewSectionLines,
197663
+ ...paddingLines,
197664
+ CURSOR_TO_0 + renderDivider(columns),
197665
+ CURSOR_TO_0 + buildGroupLine(selected, groups, columns),
197666
+ CURSOR_TO_0 + visible.fullLine,
197667
+ ...wrappedDescription.map((line) => CURSOR_TO_0 + ansi(line, DIM)),
197668
+ CURSOR_TO_0 + renderDivider(columns, getControlsLegend(searchText)),
197669
+ ""
197670
+ ];
197671
+ }
197672
+ function buildState(entries, groups, startIndex, searchText, severities) {
197673
+ return gen(function* () {
197674
+ const terminal = yield* Terminal;
197675
+ const columns = Math.max(yield* terminal.columns, 1);
197676
+ const filteredEntries = getFilteredEntries(entries, searchText);
197677
+ const normalizedStartIndex = normalizeStartIndex(filteredEntries.length, startIndex);
197678
+ return {
197679
+ startIndex: normalizedStartIndex,
197680
+ searchText,
197681
+ severities,
197682
+ renderedColumns: columns,
197683
+ renderedLines: buildFrame(filteredEntries, groups, searchText, severities, normalizedStartIndex, columns)
197684
+ };
197685
+ });
197686
+ }
197687
+ function renderSubmission(state, entries) {
197688
+ return succeed(CURSOR_TO_0 + JSON.stringify(Object.fromEntries(entries.flatMap((entry) => {
197689
+ const severity = state.severities[entry.name];
197690
+ return severity !== void 0 && severity !== entry.defaultSeverity ? [[entry.name, severity]] : [];
197691
+ }))) + "\n");
197692
+ }
197693
+ function handleProcess(entries, groups) {
196900
197694
  return (input, state) => {
196901
- const totalCount = options.rules.length;
197695
+ const filteredEntries = getFilteredEntries(entries, state.searchText);
196902
197696
  switch (input.key.name) {
196903
- case "k":
196904
- case "up": return processCursorUp(state, totalCount);
196905
- case "j":
196906
- case "down": return processCursorDown(state, totalCount);
196907
- case "left": return processSeverityChange(state, options, "left");
196908
- case "right": return processSeverityChange(state, options, "right");
197697
+ case "backspace": return buildState(entries, groups, 0, state.searchText.slice(0, -1), state.severities).pipe(map$2((nextState) => Action.NextFrame({ state: nextState })));
197698
+ case "left":
197699
+ if (filteredEntries.length === 0) return succeed(Action.Beep());
197700
+ return buildState(entries, groups, state.startIndex - 1, state.searchText, state.severities).pipe(map$2((nextState) => Action.NextFrame({ state: nextState })));
197701
+ case "right":
197702
+ if (filteredEntries.length === 0) return succeed(Action.Beep());
197703
+ return buildState(entries, groups, state.startIndex + 1, state.searchText, state.severities).pipe(map$2((nextState) => Action.NextFrame({ state: nextState })));
197704
+ case "up":
197705
+ case "down":
197706
+ if (filteredEntries.length === 0) return succeed(Action.Beep());
197707
+ return buildState(entries, groups, state.startIndex, state.searchText, {
197708
+ ...state.severities,
197709
+ [filteredEntries[state.startIndex].name]: cycleSeverity(state.severities[filteredEntries[state.startIndex].name] ?? filteredEntries[state.startIndex].defaultSeverity, input.key.name === "up" ? "left" : "right")
197710
+ }).pipe(map$2((nextState) => Action.NextFrame({ state: nextState })));
196909
197711
  case "enter":
196910
- case "return": return succeed(Action.Submit({ value: state.severities }));
196911
- default: return succeed(Action.Beep());
197712
+ case "return": return succeed(Action.Submit({ value: Object.fromEntries(entries.flatMap((entry) => {
197713
+ const severity = state.severities[entry.name];
197714
+ return severity !== void 0 && severity !== entry.defaultSeverity ? [[entry.name, severity]] : [];
197715
+ })) }));
197716
+ default:
197717
+ if (!isPrintableInput(input)) return succeed(Action.Beep());
197718
+ return buildState(entries, groups, 0, state.searchText + input.input, state.severities).pipe(map$2((nextState) => Action.NextFrame({ state: nextState })));
196912
197719
  }
196913
197720
  };
196914
197721
  }
196915
- function handleClear(options) {
196916
- return gen(function* () {
196917
- const columns = yield* (yield* Terminal).columns;
196918
- const visibleCount = Math.min(options.rules.length, options.maxPerPage);
196919
- return eraseText("\n".repeat(visibleCount + 2) + options.message, columns) + ERASE_LINE + CURSOR_LEFT;
196920
- });
196921
- }
196922
- function handleRender(options) {
196923
- return (state, action) => Action.$match(action, {
196924
- Beep: () => succeed(BEEP),
196925
- NextFrame: ({ state }) => renderNextFrame(state, options),
196926
- Submit: () => renderSubmission(state, options)
196927
- });
197722
+ function getPromptEntries(rules) {
197723
+ return rules.map((rule) => ({
197724
+ name: rule.name,
197725
+ group: rule.group,
197726
+ description: rule.description,
197727
+ previewSourceText: rule.preview.sourceText,
197728
+ previewDiagnostics: rule.preview.diagnostics,
197729
+ defaultSeverity: rule.defaultSeverity
197730
+ }));
196928
197731
  }
196929
197732
  function createRulePrompt(rules, initialSeverities) {
196930
- const options = {
196931
- message: "Configure Rule Severities",
196932
- rules,
196933
- maxPerPage: 10
196934
- };
196935
- return custom({
196936
- index: 0,
196937
- severities: initialSeverities
196938
- }, {
196939
- render: handleRender(options),
196940
- process: handleProcess(options),
196941
- clear: () => handleClear(options)
197733
+ const entries = getPromptEntries(rules);
197734
+ const groups = diagnosticGroups;
197735
+ return custom(buildState(entries, groups, 0, "", initialSeverities), {
197736
+ render: (state, action) => {
197737
+ switch (action._tag) {
197738
+ case "Beep": return succeed(BEEP);
197739
+ case "NextFrame": return succeed(action.state.renderedLines.join("\n"));
197740
+ case "Submit": return renderSubmission(state, entries);
197741
+ }
197742
+ },
197743
+ process: handleProcess(entries, groups),
197744
+ clear: (state) => gen(function* () {
197745
+ const columns = yield* (yield* Terminal).columns;
197746
+ return eraseRenderedLines(state.renderedLines, columns);
197747
+ })
196942
197748
  });
196943
197749
  }
196944
197750
 
@@ -196987,16 +197793,17 @@ const gatherTargetState = (assessment, context) => gen(function* () {
196987
197793
  editors: []
196988
197794
  };
196989
197795
  const diagnosticSeverities = (yield* select({
196990
- message: "Would you like to customize the diagnostic severities?",
197796
+ message: "Would you like to customize the diagnostics that the language service will provide?",
196991
197797
  choices: [{
196992
197798
  title: "Yes",
196993
- description: "Review the available rules and adjust their severities",
197799
+ description: "Manually review and select which diagnostics to enable",
196994
197800
  value: true,
196995
197801
  selected: true
196996
197802
  }, {
196997
197803
  title: "No",
196998
- description: "Keep the default severities shipped by @effect/tsgo",
196999
- value: false
197804
+ description: "Keep the defaults provided by the language service",
197805
+ value: false,
197806
+ selected: false
197000
197807
  }]
197001
197808
  })) ? some(yield* createRulePrompt(getAllRules(), getOrElse$1(assessment.tsconfig.currentDiagnosticSeverities, () => ({})))) : none$3();
197002
197809
  const hasVscodeSettings = isSome(assessment.vscodeSettings);