@effect/language-service 0.64.0 → 0.65.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.
@@ -26,7 +26,7 @@ __export(effect_lsp_patch_utils_exports, {
26
26
  });
27
27
  module.exports = __toCommonJS(effect_lsp_patch_utils_exports);
28
28
 
29
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/Function.js
29
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/Function.js
30
30
  var isFunction = (input) => typeof input === "function";
31
31
  var dual = function(arity, body) {
32
32
  if (typeof arity === "function") {
@@ -122,7 +122,7 @@ function pipe(a, ab, bc, cd, de, ef, fg, gh, hi) {
122
122
  }
123
123
  }
124
124
 
125
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/GlobalValue.js
125
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/GlobalValue.js
126
126
  var globalStoreId = `effect/GlobalValue`;
127
127
  var globalStore;
128
128
  var globalValue = (id, compute) => {
@@ -136,7 +136,7 @@ var globalValue = (id, compute) => {
136
136
  return globalStore.get(id);
137
137
  };
138
138
 
139
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/Predicate.js
139
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/Predicate.js
140
140
  var isString = (input) => typeof input === "string";
141
141
  var isNumber = (input) => typeof input === "number";
142
142
  var isBoolean = (input) => typeof input === "boolean";
@@ -146,7 +146,7 @@ var isObject = (input) => isRecordOrArray(input) || isFunction2(input);
146
146
  var hasProperty = /* @__PURE__ */ dual(2, (self, property) => isObject(self) && property in self);
147
147
  var isRecord = (input) => isRecordOrArray(input) && !Array.isArray(input);
148
148
 
149
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/Utils.js
149
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/Utils.js
150
150
  var GenKindTypeId = /* @__PURE__ */ Symbol.for("effect/Gen/GenKind");
151
151
  var GenKindImpl = class {
152
152
  value;
@@ -268,7 +268,7 @@ var internalCall = isNotOptimizedAway ? standard.effect_internal_function : forc
268
268
  var genConstructor = function* () {
269
269
  }.constructor;
270
270
 
271
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/Hash.js
271
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/Hash.js
272
272
  var randomHashCache = /* @__PURE__ */ globalValue(/* @__PURE__ */ Symbol.for("effect/Hash/randomHashCache"), () => /* @__PURE__ */ new WeakMap());
273
273
  var symbol = /* @__PURE__ */ Symbol.for("effect/Hash");
274
274
  var hash = (self) => {
@@ -370,7 +370,7 @@ var cached = function() {
370
370
  return hash2;
371
371
  };
372
372
 
373
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/Equal.js
373
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/Equal.js
374
374
  var symbol2 = /* @__PURE__ */ Symbol.for("effect/Equal");
375
375
  function equals() {
376
376
  if (arguments.length === 1) {
@@ -426,7 +426,7 @@ function compareBoth(self, that) {
426
426
  var isEqual = (u) => hasProperty(u, symbol2);
427
427
  var equivalence = () => equals;
428
428
 
429
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/Inspectable.js
429
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/Inspectable.js
430
430
  var NodeInspectSymbol = /* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom");
431
431
  var toJSON = (x) => {
432
432
  try {
@@ -478,7 +478,7 @@ var redact = (u) => {
478
478
  return u;
479
479
  };
480
480
 
481
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/Pipeable.js
481
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/Pipeable.js
482
482
  var pipeArguments = (self, args2) => {
483
483
  switch (args2.length) {
484
484
  case 0:
@@ -511,14 +511,14 @@ var pipeArguments = (self, args2) => {
511
511
  }
512
512
  };
513
513
 
514
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/internal/opCodes/effect.js
514
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/internal/opCodes/effect.js
515
515
  var OP_COMMIT = "Commit";
516
516
 
517
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/internal/version.js
518
- var moduleVersion = "3.19.13";
517
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/internal/version.js
518
+ var moduleVersion = "3.19.14";
519
519
  var getCurrentVersion = () => moduleVersion;
520
520
 
521
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/internal/effectable.js
521
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/internal/effectable.js
522
522
  var EffectTypeId = /* @__PURE__ */ Symbol.for("effect/Effect");
523
523
  var StreamTypeId = /* @__PURE__ */ Symbol.for("effect/Stream");
524
524
  var SinkTypeId = /* @__PURE__ */ Symbol.for("effect/Sink");
@@ -605,7 +605,7 @@ var StructuralCommitPrototype = {
605
605
  ...StructuralPrototype
606
606
  };
607
607
 
608
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/internal/option.js
608
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/internal/option.js
609
609
  var TypeId = /* @__PURE__ */ Symbol.for("effect/Option");
610
610
  var CommonProto = {
611
611
  ...EffectPrototype,
@@ -663,7 +663,7 @@ var some = (value) => {
663
663
  return a;
664
664
  };
665
665
 
666
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/internal/either.js
666
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/internal/either.js
667
667
  var TypeId2 = /* @__PURE__ */ Symbol.for("effect/Either");
668
668
  var CommonProto2 = {
669
669
  ...EffectPrototype,
@@ -725,7 +725,7 @@ var right = (right3) => {
725
725
  return a;
726
726
  };
727
727
 
728
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/Either.js
728
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/Either.js
729
729
  var right2 = right;
730
730
  var left2 = left;
731
731
  var isLeft2 = isLeft;
@@ -733,14 +733,14 @@ var isRight2 = isRight;
733
733
  var map = /* @__PURE__ */ dual(2, (self, f) => isRight2(self) ? right2(f(self.right)) : left2(self.left));
734
734
  var getOrElse = /* @__PURE__ */ dual(2, (self, onLeft) => isLeft2(self) ? onLeft(self.left) : self.right);
735
735
 
736
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/internal/array.js
736
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/internal/array.js
737
737
  var isNonEmptyArray = (self) => self.length > 0;
738
738
 
739
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/Order.js
739
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/Order.js
740
740
  var make = (compare) => (self, that) => self === that ? 0 : compare(self, that);
741
741
  var string2 = /* @__PURE__ */ make((self, that) => self < that ? -1 : 1);
742
742
 
743
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/Option.js
743
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/Option.js
744
744
  var none2 = () => none;
745
745
  var some2 = some;
746
746
  var isNone2 = isNone;
@@ -750,7 +750,7 @@ var orElse = /* @__PURE__ */ dual(2, (self, that) => isNone2(self) ? that() : se
750
750
  var fromNullable = (nullableValue) => nullableValue == null ? none2() : some2(nullableValue);
751
751
  var getOrUndefined = /* @__PURE__ */ getOrElse2(constUndefined);
752
752
 
753
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/Record.js
753
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/Record.js
754
754
  var map2 = /* @__PURE__ */ dual(2, (self, f) => {
755
755
  const out = {
756
756
  ...self
@@ -762,7 +762,7 @@ var map2 = /* @__PURE__ */ dual(2, (self, f) => {
762
762
  });
763
763
  var keys = (self) => Object.keys(self);
764
764
 
765
- // node_modules/.pnpm/effect@3.19.13/node_modules/effect/dist/esm/Array.js
765
+ // node_modules/.pnpm/effect@3.19.14/node_modules/effect/dist/esm/Array.js
766
766
  var fromIterable = (collection) => Array.isArray(collection) ? collection : Array.from(collection);
767
767
  var append = /* @__PURE__ */ dual(2, (self, last) => [...self, last]);
768
768
  var appendAll = /* @__PURE__ */ dual(2, (self, that) => fromIterable(self).concat(fromIterable(that)));
@@ -785,7 +785,6 @@ var unsafeGet = /* @__PURE__ */ dual(2, (self, index) => {
785
785
  var head = /* @__PURE__ */ get(0);
786
786
  var headNonEmpty = /* @__PURE__ */ unsafeGet(0);
787
787
  var tailNonEmpty = (self) => self.slice(1);
788
- var reverse = (self) => Array.from(self).reverse();
789
788
  var sort = /* @__PURE__ */ dual(2, (self, O) => {
790
789
  const out = Array.from(self);
791
790
  out.sort(O);
@@ -1201,7 +1200,7 @@ var defaults = {
1201
1200
  diagnosticSeverity: {},
1202
1201
  diagnosticsName: true,
1203
1202
  missingDiagnosticNextLine: "warning",
1204
- reportSuggestionsAsWarningsInTsc: false,
1203
+ includeSuggestionsInTsc: true,
1205
1204
  quickinfo: true,
1206
1205
  quickinfoEffectParameters: "whentruncated",
1207
1206
  quickinfoMaximumLength: -1,
@@ -1225,7 +1224,7 @@ var defaults = {
1225
1224
  skipLeadingPath: ["src/"]
1226
1225
  }],
1227
1226
  extendedKeyDetection: false,
1228
- pipeableMinArgCount: 1,
1227
+ pipeableMinArgCount: 2,
1229
1228
  layerGraphFollowDepth: 0,
1230
1229
  mermaidProvider: "mermaid.live"
1231
1230
  };
@@ -1248,7 +1247,7 @@ function parse(config) {
1248
1247
  diagnosticSeverity: isObject(config) && hasProperty(config, "diagnosticSeverity") && isRecord(config.diagnosticSeverity) ? parseDiagnosticSeverity(config.diagnosticSeverity) : defaults.diagnosticSeverity,
1249
1248
  diagnosticsName: isObject(config) && hasProperty(config, "diagnosticsName") && isBoolean(config.diagnosticsName) ? config.diagnosticsName : defaults.diagnosticsName,
1250
1249
  missingDiagnosticNextLine: isObject(config) && hasProperty(config, "missingDiagnosticNextLine") && isString(config.missingDiagnosticNextLine) && isValidSeverityLevel(config.missingDiagnosticNextLine) ? config.missingDiagnosticNextLine : defaults.missingDiagnosticNextLine,
1251
- reportSuggestionsAsWarningsInTsc: isObject(config) && hasProperty(config, "reportSuggestionsAsWarningsInTsc") && isBoolean(config.reportSuggestionsAsWarningsInTsc) ? config.reportSuggestionsAsWarningsInTsc : defaults.reportSuggestionsAsWarningsInTsc,
1250
+ includeSuggestionsInTsc: isObject(config) && hasProperty(config, "includeSuggestionsInTsc") && isBoolean(config.includeSuggestionsInTsc) ? config.includeSuggestionsInTsc : defaults.includeSuggestionsInTsc,
1252
1251
  quickinfo: isObject(config) && hasProperty(config, "quickinfo") && isBoolean(config.quickinfo) ? config.quickinfo : defaults.quickinfo,
1253
1252
  quickinfoEffectParameters: isObject(config) && hasProperty(config, "quickinfoEffectParameters") && isString(config.quickinfoEffectParameters) && ["always", "never", "whentruncated"].includes(config.quickinfoEffectParameters.toLowerCase()) ? config.quickinfoEffectParameters.toLowerCase() : defaults.quickinfoEffectParameters,
1254
1253
  quickinfoMaximumLength: isObject(config) && hasProperty(config, "quickinfoMaximumLength") && isNumber(config.quickinfoMaximumLength) ? config.quickinfoMaximumLength : defaults.quickinfoMaximumLength,
@@ -1818,6 +1817,16 @@ function makeTypeScriptUtils(ts) {
1818
1817
  function isDeclarationKind(kind) {
1819
1818
  return kind === ts.SyntaxKind.ArrowFunction || kind === ts.SyntaxKind.BindingElement || kind === ts.SyntaxKind.ClassDeclaration || kind === ts.SyntaxKind.ClassExpression || kind === ts.SyntaxKind.ClassStaticBlockDeclaration || kind === ts.SyntaxKind.Constructor || kind === ts.SyntaxKind.EnumDeclaration || kind === ts.SyntaxKind.EnumMember || kind === ts.SyntaxKind.ExportSpecifier || kind === ts.SyntaxKind.FunctionDeclaration || kind === ts.SyntaxKind.FunctionExpression || kind === ts.SyntaxKind.GetAccessor || kind === ts.SyntaxKind.ImportClause || kind === ts.SyntaxKind.ImportEqualsDeclaration || kind === ts.SyntaxKind.ImportSpecifier || kind === ts.SyntaxKind.InterfaceDeclaration || kind === ts.SyntaxKind.JsxAttribute || kind === ts.SyntaxKind.MethodDeclaration || kind === ts.SyntaxKind.MethodSignature || kind === ts.SyntaxKind.ModuleDeclaration || kind === ts.SyntaxKind.NamespaceExportDeclaration || kind === ts.SyntaxKind.NamespaceImport || kind === ts.SyntaxKind.NamespaceExport || kind === ts.SyntaxKind.Parameter || kind === ts.SyntaxKind.PropertyAssignment || kind === ts.SyntaxKind.PropertyDeclaration || kind === ts.SyntaxKind.PropertySignature || kind === ts.SyntaxKind.SetAccessor || kind === ts.SyntaxKind.ShorthandPropertyAssignment || kind === ts.SyntaxKind.TypeAliasDeclaration || kind === ts.SyntaxKind.TypeParameter || kind === ts.SyntaxKind.VariableDeclaration || kind === ts.SyntaxKind.JSDocTypedefTag || kind === ts.SyntaxKind.JSDocCallbackTag || kind === ts.SyntaxKind.JSDocPropertyTag || kind === ts.SyntaxKind.NamedTupleMember;
1820
1819
  }
1820
+ function isVoidExpression(node) {
1821
+ const unwrapped = ts.isExpression(node) ? skipOuterExpressions(node) : node;
1822
+ if (ts.isVoidExpression(unwrapped) && ts.isNumericLiteral(unwrapped.expression) && unwrapped.expression.text === "0") {
1823
+ return true;
1824
+ }
1825
+ if (ts.isIdentifier(unwrapped) && ts.idText(unwrapped) === "undefined") {
1826
+ return true;
1827
+ }
1828
+ return false;
1829
+ }
1821
1830
  return {
1822
1831
  findNodeAtPositionIncludingTrivia,
1823
1832
  parsePackageContentNameAndVersionFromScope,
@@ -1841,7 +1850,8 @@ function makeTypeScriptUtils(ts) {
1841
1850
  getSourceFileOfNode,
1842
1851
  isOuterExpression,
1843
1852
  skipOuterExpressions,
1844
- isDeclarationKind
1853
+ isDeclarationKind,
1854
+ isVoidExpression
1845
1855
  };
1846
1856
  }
1847
1857
 
@@ -3023,13 +3033,15 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3023
3033
  );
3024
3034
  }
3025
3035
  const propertyAccess = node.expression;
3036
+ const pipeArguments2 = node.arguments.slice(1);
3026
3037
  return pipe(
3027
3038
  isNodeReferenceToEffectModuleApi("fnUntraced")(propertyAccess),
3028
3039
  map4(() => ({
3029
3040
  node,
3030
3041
  effectModule: propertyAccess.expression,
3031
3042
  generatorFunction,
3032
- body: generatorFunction.body
3043
+ body: generatorFunction.body,
3044
+ pipeArguments: pipeArguments2
3033
3045
  }))
3034
3046
  );
3035
3047
  },
@@ -3068,13 +3080,15 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3068
3080
  );
3069
3081
  }
3070
3082
  const propertyAccess = expressionToTest;
3083
+ const pipeArguments2 = node.arguments.slice(1);
3071
3084
  return pipe(
3072
3085
  isNodeReferenceToEffectModuleApi("fn")(propertyAccess),
3073
3086
  map4(() => ({
3074
3087
  node,
3075
3088
  generatorFunction,
3076
3089
  effectModule: propertyAccess.expression,
3077
- body: generatorFunction.body
3090
+ body: generatorFunction.body,
3091
+ pipeArguments: pipeArguments2
3078
3092
  }))
3079
3093
  );
3080
3094
  },
@@ -3261,6 +3275,23 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3261
3275
  "TypeParser.pipeCall",
3262
3276
  (node) => node
3263
3277
  );
3278
+ const singleArgCall = cachedBy(
3279
+ function(node) {
3280
+ if (!ts.isCallExpression(node)) {
3281
+ return typeParserIssue("Node is not a call expression", void 0, node);
3282
+ }
3283
+ if (node.arguments.length !== 1) {
3284
+ return typeParserIssue("Node must have exactly one argument", void 0, node);
3285
+ }
3286
+ return succeed({
3287
+ node,
3288
+ callee: node.expression,
3289
+ subject: node.arguments[0]
3290
+ });
3291
+ },
3292
+ "TypeParser.singleArgCall",
3293
+ (node) => node
3294
+ );
3264
3295
  const scopeType = cachedBy(
3265
3296
  fn("TypeParser.scopeType")(function* (type, atLocation) {
3266
3297
  yield* pipeableType(type, atLocation);
@@ -3799,6 +3830,266 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3799
3830
  `TypeParser.isNodeReferenceToEffectLayerModuleApi(${memberName})`,
3800
3831
  (node) => node
3801
3832
  );
3833
+ const lazyExpression = cachedBy(
3834
+ function(node) {
3835
+ if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node)) {
3836
+ return typeParserIssue("Node is not an arrow function or function expression", void 0, node);
3837
+ }
3838
+ if (node.parameters.length !== 0) {
3839
+ return typeParserIssue("Function must have zero parameters", void 0, node);
3840
+ }
3841
+ if (node.typeParameters && node.typeParameters.length > 0) {
3842
+ return typeParserIssue("Function must have no type parameters", void 0, node);
3843
+ }
3844
+ const body = node.body;
3845
+ const returnType = node.type;
3846
+ if (ts.isArrowFunction(node) && !ts.isBlock(body)) {
3847
+ return succeed({
3848
+ node,
3849
+ body,
3850
+ expression: body,
3851
+ returnType
3852
+ });
3853
+ }
3854
+ if (ts.isBlock(body)) {
3855
+ if (body.statements.length !== 1) {
3856
+ return typeParserIssue("Block must have exactly one statement", void 0, node);
3857
+ }
3858
+ const stmt = body.statements[0];
3859
+ if (!ts.isReturnStatement(stmt)) {
3860
+ return typeParserIssue("Statement must be a return statement", void 0, node);
3861
+ }
3862
+ if (!stmt.expression) {
3863
+ return typeParserIssue("Return statement must have an expression", void 0, node);
3864
+ }
3865
+ return succeed({
3866
+ node,
3867
+ body,
3868
+ expression: stmt.expression,
3869
+ returnType
3870
+ });
3871
+ }
3872
+ return typeParserIssue("Invalid function body", void 0, node);
3873
+ },
3874
+ "TypeParser.lazyExpression",
3875
+ (node) => node
3876
+ );
3877
+ const emptyFunction = cachedBy(
3878
+ function(node) {
3879
+ if (!ts.isArrowFunction(node) && !ts.isFunctionExpression(node)) {
3880
+ return typeParserIssue("Node is not an arrow function or function expression", void 0, node);
3881
+ }
3882
+ const body = node.body;
3883
+ const returnType = node.type;
3884
+ if (!ts.isBlock(body)) {
3885
+ return typeParserIssue("Body must be a block", void 0, node);
3886
+ }
3887
+ if (body.statements.length !== 0) {
3888
+ return typeParserIssue("Block must have zero statements", void 0, node);
3889
+ }
3890
+ return succeed({
3891
+ node,
3892
+ body,
3893
+ returnType
3894
+ });
3895
+ },
3896
+ "TypeParser.emptyFunction",
3897
+ (node) => node
3898
+ );
3899
+ const pipingFlows = (includeEffectFn) => cachedBy(
3900
+ fn("TypeParser.pipingFlows")(function* (sourceFile) {
3901
+ const result = [];
3902
+ const workQueue = [[sourceFile, void 0]];
3903
+ while (workQueue.length > 0) {
3904
+ const [node, parentFlow] = workQueue.pop();
3905
+ if (ts.isCallExpression(node)) {
3906
+ const parsed = yield* pipe(
3907
+ pipeCall(node),
3908
+ map4((p) => ({ _tag: "pipe", ...p })),
3909
+ orElse2(
3910
+ () => pipe(
3911
+ singleArgCall(node),
3912
+ map4((s) => ({ _tag: "call", ...s }))
3913
+ )
3914
+ ),
3915
+ option
3916
+ );
3917
+ if (isSome2(parsed)) {
3918
+ const result2 = parsed.value;
3919
+ let transformations;
3920
+ let flowNode;
3921
+ let childrenToTraverse = [];
3922
+ if (result2._tag === "pipe") {
3923
+ const signature = typeChecker.getResolvedSignature(result2.node);
3924
+ const typeArguments = signature ? typeChecker.getTypeArgumentsForResolvedSignature(signature) : void 0;
3925
+ transformations = [];
3926
+ for (let i = 0; i < result2.args.length; i++) {
3927
+ const arg = result2.args[i];
3928
+ const outType = typeArguments?.[i + 1];
3929
+ if (ts.isCallExpression(arg)) {
3930
+ transformations.push({
3931
+ callee: arg.expression,
3932
+ // e.g., Effect.map
3933
+ args: Array.from(arg.arguments),
3934
+ // e.g., [(x) => x + 1]
3935
+ outType,
3936
+ kind: result2.kind
3937
+ });
3938
+ } else {
3939
+ transformations.push({
3940
+ callee: arg,
3941
+ // e.g., Effect.asVoid
3942
+ args: void 0,
3943
+ outType,
3944
+ kind: result2.kind
3945
+ });
3946
+ }
3947
+ }
3948
+ flowNode = result2.node;
3949
+ childrenToTraverse = result2.args;
3950
+ } else {
3951
+ const callSignature = typeChecker.getResolvedSignature(node);
3952
+ const outType = callSignature ? typeChecker.getReturnTypeOfSignature(callSignature) : void 0;
3953
+ transformations = [{
3954
+ callee: result2.callee,
3955
+ args: void 0,
3956
+ outType,
3957
+ kind: "call"
3958
+ }];
3959
+ flowNode = node;
3960
+ }
3961
+ if (parentFlow) {
3962
+ parentFlow.transformations.unshift(...transformations);
3963
+ parentFlow.subject = {
3964
+ node: result2.subject,
3965
+ outType: typeCheckerUtils.getTypeAtLocation(result2.subject)
3966
+ };
3967
+ workQueue.push([result2.subject, parentFlow]);
3968
+ } else {
3969
+ const newFlow = {
3970
+ node: flowNode,
3971
+ subject: {
3972
+ node: result2.subject,
3973
+ outType: typeCheckerUtils.getTypeAtLocation(result2.subject)
3974
+ },
3975
+ transformations
3976
+ };
3977
+ workQueue.push([result2.subject, newFlow]);
3978
+ }
3979
+ for (const child of childrenToTraverse) {
3980
+ ts.forEachChild(child, (c) => {
3981
+ workQueue.push([c, void 0]);
3982
+ });
3983
+ }
3984
+ continue;
3985
+ }
3986
+ if (includeEffectFn) {
3987
+ const effectFnGenParsed = yield* pipe(effectFnGen(node), option);
3988
+ const effectFnUntracedGenParsed = isNone2(effectFnGenParsed) ? yield* pipe(effectFnUntracedGen(node), option) : none2();
3989
+ const isEffectFn = isSome2(effectFnGenParsed);
3990
+ const effectFnParsed = isEffectFn ? effectFnGenParsed : effectFnUntracedGenParsed;
3991
+ const transformationKind = isEffectFn ? "effectFn" : "effectFnUntraced";
3992
+ if (isSome2(effectFnParsed) && effectFnParsed.value.pipeArguments.length > 0) {
3993
+ const fnResult = effectFnParsed.value;
3994
+ const pipeArgs = fnResult.pipeArguments;
3995
+ const transformations = [];
3996
+ let subjectType;
3997
+ for (let i = 0; i < pipeArgs.length; i++) {
3998
+ const arg = pipeArgs[i];
3999
+ const contextualType = typeChecker.getContextualType(arg);
4000
+ const callSigs = contextualType ? typeChecker.getSignaturesOfType(contextualType, ts.SignatureKind.Call) : [];
4001
+ const outType = callSigs.length > 0 ? typeChecker.getReturnTypeOfSignature(callSigs[0]) : void 0;
4002
+ if (i === 0 && callSigs.length > 0) {
4003
+ const params = callSigs[0].parameters;
4004
+ if (params.length > 0) {
4005
+ subjectType = typeChecker.getTypeOfSymbol(params[0]);
4006
+ }
4007
+ }
4008
+ if (ts.isCallExpression(arg)) {
4009
+ transformations.push({
4010
+ callee: arg.expression,
4011
+ args: Array.from(arg.arguments),
4012
+ outType,
4013
+ kind: transformationKind
4014
+ });
4015
+ } else {
4016
+ transformations.push({
4017
+ callee: arg,
4018
+ args: void 0,
4019
+ outType,
4020
+ kind: transformationKind
4021
+ });
4022
+ }
4023
+ }
4024
+ const newFlow = {
4025
+ node,
4026
+ subject: {
4027
+ node,
4028
+ outType: subjectType
4029
+ },
4030
+ transformations
4031
+ };
4032
+ result.push(newFlow);
4033
+ workQueue.push([fnResult.body, void 0]);
4034
+ for (const arg of pipeArgs) {
4035
+ ts.forEachChild(arg, (c) => {
4036
+ workQueue.push([c, void 0]);
4037
+ });
4038
+ }
4039
+ continue;
4040
+ }
4041
+ }
4042
+ }
4043
+ if (parentFlow && parentFlow.transformations.length > 0) {
4044
+ result.push(parentFlow);
4045
+ }
4046
+ ts.forEachChild(node, (child) => {
4047
+ workQueue.push([child, void 0]);
4048
+ });
4049
+ }
4050
+ result.sort((a, b) => a.node.pos - b.node.pos);
4051
+ return result;
4052
+ }),
4053
+ `TypeParser.pipingFlows(${includeEffectFn})`,
4054
+ (sourceFile) => sourceFile
4055
+ );
4056
+ const reconstructPipingFlow = (flow2) => {
4057
+ if (flow2.transformations.length > 0 && flow2.transformations.every((t) => t.kind === "effectFn" || t.kind === "effectFnUntraced")) {
4058
+ return flow2.subject.node;
4059
+ }
4060
+ let result = flow2.subject.node;
4061
+ for (const t of flow2.transformations) {
4062
+ if (t.kind === "call") {
4063
+ result = ts.factory.createCallExpression(
4064
+ t.callee,
4065
+ void 0,
4066
+ [result]
4067
+ );
4068
+ } else if (t.kind === "effectFn" || t.kind === "effectFnUntraced") {
4069
+ continue;
4070
+ } else {
4071
+ if (t.args) {
4072
+ const transformCall = ts.factory.createCallExpression(
4073
+ t.callee,
4074
+ void 0,
4075
+ t.args
4076
+ );
4077
+ result = ts.factory.createCallExpression(
4078
+ transformCall,
4079
+ void 0,
4080
+ [result]
4081
+ );
4082
+ } else {
4083
+ result = ts.factory.createCallExpression(
4084
+ t.callee,
4085
+ void 0,
4086
+ [result]
4087
+ );
4088
+ }
4089
+ }
4090
+ }
4091
+ return result;
4092
+ };
3802
4093
  return {
3803
4094
  isNodeReferenceToEffectModuleApi,
3804
4095
  isNodeReferenceToEffectSchemaModuleApi,
@@ -3821,6 +4112,7 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3821
4112
  contextTag,
3822
4113
  pipeableType,
3823
4114
  pipeCall,
4115
+ singleArgCall,
3824
4116
  scopeType,
3825
4117
  promiseLike,
3826
4118
  extendsEffectTag,
@@ -3832,7 +4124,11 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3832
4124
  extendsDataTaggedError,
3833
4125
  extendsDataTaggedClass,
3834
4126
  extendsSchemaTaggedRequest,
3835
- extendsEffectSqlModelClass
4127
+ extendsEffectSqlModelClass,
4128
+ lazyExpression,
4129
+ emptyFunction,
4130
+ pipingFlows,
4131
+ reconstructPipingFlow
3836
4132
  };
3837
4133
  }
3838
4134
 
@@ -3986,48 +4282,45 @@ var catchAllToMapError = createDiagnostic({
3986
4282
  return none2();
3987
4283
  });
3988
4284
  };
3989
- const nodeToVisit = [];
3990
- const appendNodeToVisit = (node) => {
3991
- nodeToVisit.push(node);
3992
- return void 0;
3993
- };
3994
- ts.forEachChild(sourceFile, appendNodeToVisit);
3995
- while (nodeToVisit.length > 0) {
3996
- const node = nodeToVisit.shift();
3997
- ts.forEachChild(node, appendNodeToVisit);
3998
- if (ts.isCallExpression(node)) {
4285
+ const flows = yield* typeParser.pipingFlows(true)(sourceFile);
4286
+ for (const flow2 of flows) {
4287
+ for (const transformation of flow2.transformations) {
4288
+ if (!transformation.args || transformation.args.length === 0) {
4289
+ continue;
4290
+ }
3999
4291
  const isCatchAllCall = yield* pipe(
4000
- typeParser.isNodeReferenceToEffectModuleApi("catchAll")(node.expression),
4292
+ typeParser.isNodeReferenceToEffectModuleApi("catchAll")(transformation.callee),
4001
4293
  option
4002
4294
  );
4003
- if (isSome2(isCatchAllCall)) {
4004
- const callback = node.arguments[0];
4005
- if (!callback) continue;
4006
- const functionBody = getFunctionBody(callback);
4007
- if (!functionBody) continue;
4008
- const failCallInfo = yield* getEffectFailCallInfo(functionBody);
4009
- if (isNone2(failCallInfo)) continue;
4010
- const { failArg, failCall } = failCallInfo.value;
4011
- report({
4012
- location: node.expression,
4013
- messageText: `You can use Effect.mapError instead of Effect.catchAll + Effect.fail to transform the error type.`,
4014
- fixes: [{
4015
- fixName: "catchAllToMapError_fix",
4016
- description: "Replace with Effect.mapError",
4017
- apply: gen(function* () {
4018
- const changeTracker = yield* service(ChangeTracker);
4019
- if (ts.isPropertyAccessExpression(node.expression)) {
4020
- changeTracker.replaceNode(
4021
- sourceFile,
4022
- node.expression.name,
4023
- ts.factory.createIdentifier("mapError")
4024
- );
4025
- }
4026
- changeTracker.replaceNode(sourceFile, failCall, failArg);
4027
- })
4028
- }]
4029
- });
4295
+ if (isNone2(isCatchAllCall)) {
4296
+ continue;
4030
4297
  }
4298
+ const callback = transformation.args[0];
4299
+ if (!callback) continue;
4300
+ const functionBody = getFunctionBody(callback);
4301
+ if (!functionBody) continue;
4302
+ const failCallInfo = yield* getEffectFailCallInfo(functionBody);
4303
+ if (isNone2(failCallInfo)) continue;
4304
+ const { failArg, failCall } = failCallInfo.value;
4305
+ report({
4306
+ location: transformation.callee,
4307
+ messageText: `You can use Effect.mapError instead of Effect.catchAll + Effect.fail to transform the error type.`,
4308
+ fixes: [{
4309
+ fixName: "catchAllToMapError_fix",
4310
+ description: "Replace with Effect.mapError",
4311
+ apply: gen(function* () {
4312
+ const changeTracker = yield* service(ChangeTracker);
4313
+ if (ts.isPropertyAccessExpression(transformation.callee)) {
4314
+ changeTracker.replaceNode(
4315
+ sourceFile,
4316
+ transformation.callee.name,
4317
+ ts.factory.createIdentifier("mapError")
4318
+ );
4319
+ }
4320
+ changeTracker.replaceNode(sourceFile, failCall, failArg);
4321
+ })
4322
+ }]
4323
+ });
4031
4324
  }
4032
4325
  }
4033
4326
  })
@@ -4042,66 +4335,39 @@ var catchUnfailableEffect = createDiagnostic({
4042
4335
  apply: fn("catchUnfailableEffect.apply")(function* (sourceFile, report) {
4043
4336
  const ts = yield* service(TypeScriptApi);
4044
4337
  const typeParser = yield* service(TypeParser);
4045
- const typeChecker = yield* service(TypeCheckerApi);
4046
- const typeCheckerUtils = yield* service(TypeCheckerUtils);
4047
- const nodeToVisit = [];
4048
- const appendNodeToVisit = (node) => {
4049
- nodeToVisit.push(node);
4050
- return void 0;
4051
- };
4052
- ts.forEachChild(sourceFile, appendNodeToVisit);
4053
- while (nodeToVisit.length > 0) {
4054
- const node = nodeToVisit.shift();
4055
- ts.forEachChild(node, appendNodeToVisit);
4056
- if (ts.isCallExpression(node)) {
4057
- const catchFunctions = ["catchAll", "catch", "catchIf", "catchSome", "catchTag", "catchTags"];
4338
+ const catchFunctions = ["catchAll", "catch", "catchIf", "catchSome", "catchTag", "catchTags"];
4339
+ const flows = yield* typeParser.pipingFlows(true)(sourceFile);
4340
+ for (const flow2 of flows) {
4341
+ for (let i = 0; i < flow2.transformations.length; i++) {
4342
+ const transformation = flow2.transformations[i];
4343
+ if (!transformation.args || transformation.args.length === 0) {
4344
+ continue;
4345
+ }
4058
4346
  const isCatchCall = yield* pipe(
4059
4347
  firstSuccessOf(
4060
- catchFunctions.map((catchFn) => typeParser.isNodeReferenceToEffectModuleApi(catchFn)(node.expression))
4348
+ catchFunctions.map((catchFn) => typeParser.isNodeReferenceToEffectModuleApi(catchFn)(transformation.callee))
4061
4349
  ),
4062
4350
  option
4063
4351
  );
4064
- if (isSome2(isCatchCall)) {
4065
- const parent = node.parent;
4066
- if (parent && ts.isCallExpression(parent)) {
4067
- const pipeCallResult = yield* pipe(
4068
- typeParser.pipeCall(parent),
4069
- option
4070
- );
4071
- if (isSome2(pipeCallResult)) {
4072
- const { args: args2, node: pipeCallNode, subject } = pipeCallResult.value;
4073
- const argIndex = args2.findIndex((arg) => arg === node);
4074
- if (argIndex !== -1) {
4075
- let effectTypeToCheck;
4076
- if (argIndex === 0) {
4077
- effectTypeToCheck = typeCheckerUtils.getTypeAtLocation(subject);
4078
- } else {
4079
- const signature = typeChecker.getResolvedSignature(pipeCallNode);
4080
- if (signature) {
4081
- const typeArguments = typeChecker.getTypeArgumentsForResolvedSignature(signature);
4082
- if (typeArguments && typeArguments.length > argIndex) {
4083
- effectTypeToCheck = typeArguments[argIndex];
4084
- }
4085
- }
4086
- }
4087
- if (effectTypeToCheck) {
4088
- const effectType = yield* pipe(
4089
- typeParser.effectType(effectTypeToCheck, node),
4090
- option
4091
- );
4092
- if (isSome2(effectType)) {
4093
- const { E } = effectType.value;
4094
- if (E.flags & ts.TypeFlags.Never) {
4095
- report({
4096
- location: node.expression,
4097
- messageText: `Looks like the previous effect never fails, so probably this error handling will never be triggered.`,
4098
- fixes: []
4099
- });
4100
- }
4101
- }
4102
- }
4103
- }
4104
- }
4352
+ if (isNone2(isCatchCall)) {
4353
+ continue;
4354
+ }
4355
+ const inputType = i === 0 ? flow2.subject.outType : flow2.transformations[i - 1].outType;
4356
+ if (!inputType) {
4357
+ continue;
4358
+ }
4359
+ const effectType = yield* pipe(
4360
+ typeParser.effectType(inputType, transformation.callee),
4361
+ option
4362
+ );
4363
+ if (isSome2(effectType)) {
4364
+ const { E } = effectType.value;
4365
+ if (E.flags & ts.TypeFlags.Never) {
4366
+ report({
4367
+ location: transformation.callee,
4368
+ messageText: `Looks like the previous effect never fails, so probably this error handling will never be triggered.`,
4369
+ fixes: []
4370
+ });
4105
4371
  }
4106
4372
  }
4107
4373
  }
@@ -4410,6 +4676,245 @@ ${versions.map((version) => `- found ${version} at ${resolvedPackages[packageNam
4410
4676
  })
4411
4677
  });
4412
4678
 
4679
+ // src/diagnostics/effectFnOpportunity.ts
4680
+ var parseEffectFnOpportunityTarget = (node, sourceFile) => gen(function* () {
4681
+ const ts = yield* service(TypeScriptApi);
4682
+ const typeChecker = yield* service(TypeCheckerApi);
4683
+ const typeParser = yield* service(TypeParser);
4684
+ const tsUtils = yield* service(TypeScriptUtils);
4685
+ if (!ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isFunctionDeclaration(node)) {
4686
+ return yield* TypeParserIssue.issue;
4687
+ }
4688
+ if ((ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) && node.asteriskToken) {
4689
+ return yield* TypeParserIssue.issue;
4690
+ }
4691
+ if (ts.isFunctionExpression(node) && node.name) {
4692
+ return yield* TypeParserIssue.issue;
4693
+ }
4694
+ let bodyExpression;
4695
+ if (ts.isArrowFunction(node)) {
4696
+ if (ts.isBlock(node.body)) {
4697
+ const returnStatement = findSingleReturnStatement(ts, node.body);
4698
+ if (returnStatement?.expression) {
4699
+ bodyExpression = returnStatement.expression;
4700
+ }
4701
+ } else {
4702
+ bodyExpression = node.body;
4703
+ }
4704
+ } else if ((ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) && node.body) {
4705
+ const returnStatement = findSingleReturnStatement(ts, node.body);
4706
+ if (returnStatement?.expression) {
4707
+ bodyExpression = returnStatement.expression;
4708
+ }
4709
+ }
4710
+ if (!bodyExpression) return yield* TypeParserIssue.issue;
4711
+ const { pipeArguments: pipeArguments2, subject } = yield* pipe(
4712
+ typeParser.pipeCall(bodyExpression),
4713
+ map4(({ args: args2, subject: subject2 }) => ({ subject: subject2, pipeArguments: args2 })),
4714
+ orElse2(() => succeed({ subject: bodyExpression, pipeArguments: [] }))
4715
+ );
4716
+ const { effectModule, generatorFunction } = yield* typeParser.effectGen(subject);
4717
+ const functionType = typeChecker.getTypeAtLocation(node);
4718
+ if (!functionType) return yield* TypeParserIssue.issue;
4719
+ const callSignatures = typeChecker.getSignaturesOfType(functionType, ts.SignatureKind.Call);
4720
+ if (callSignatures.length !== 1) return yield* TypeParserIssue.issue;
4721
+ const signature = callSignatures[0];
4722
+ const returnType = typeChecker.getReturnTypeOfSignature(signature);
4723
+ const { A, E, R } = yield* typeParser.strictEffectType(returnType, node);
4724
+ const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
4725
+ sourceFile,
4726
+ "effect",
4727
+ "Effect"
4728
+ ) || "Effect";
4729
+ const nameIdentifier = getNameIdentifier(ts, node);
4730
+ const traceName = nameIdentifier ? ts.isIdentifier(nameIdentifier) ? ts.idText(nameIdentifier) : nameIdentifier.text : void 0;
4731
+ const hasReturnTypeAnnotation = !!node.type;
4732
+ return {
4733
+ node,
4734
+ nameIdentifier,
4735
+ effectModule,
4736
+ generatorFunction,
4737
+ effectModuleName,
4738
+ traceName,
4739
+ hasReturnTypeAnnotation,
4740
+ effectTypes: { A, E, R },
4741
+ pipeArguments: pipeArguments2
4742
+ };
4743
+ });
4744
+ var effectFnOpportunity = createDiagnostic({
4745
+ name: "effectFnOpportunity",
4746
+ code: 41,
4747
+ description: "Suggests using Effect.fn for functions that return Effect.gen",
4748
+ severity: "suggestion",
4749
+ apply: fn("effectFnOpportunity.apply")(function* (sourceFile, report) {
4750
+ const ts = yield* service(TypeScriptApi);
4751
+ const typeChecker = yield* service(TypeCheckerApi);
4752
+ const tsUtils = yield* service(TypeScriptUtils);
4753
+ const createReturnTypeAnnotation = (effectModuleName, effectTypes, enclosingNode) => {
4754
+ const { A, E, R } = effectTypes;
4755
+ const aTypeNode = typeChecker.typeToTypeNode(A, enclosingNode, ts.NodeBuilderFlags.NoTruncation);
4756
+ const eTypeNode = typeChecker.typeToTypeNode(E, enclosingNode, ts.NodeBuilderFlags.NoTruncation);
4757
+ const rTypeNode = typeChecker.typeToTypeNode(R, enclosingNode, ts.NodeBuilderFlags.NoTruncation);
4758
+ if (!aTypeNode || !eTypeNode || !rTypeNode) return void 0;
4759
+ return ts.factory.createTypeReferenceNode(
4760
+ ts.factory.createQualifiedName(
4761
+ ts.factory.createQualifiedName(
4762
+ ts.factory.createIdentifier(effectModuleName),
4763
+ "fn"
4764
+ ),
4765
+ "Return"
4766
+ ),
4767
+ [aTypeNode, eTypeNode, rTypeNode]
4768
+ );
4769
+ };
4770
+ const createEffectFnNode = (originalNode, generatorFunction, effectModuleName, traceName, effectTypes, pipeArguments2) => {
4771
+ const returnTypeAnnotation = effectTypes ? createReturnTypeAnnotation(effectModuleName, effectTypes, originalNode) : void 0;
4772
+ const newGeneratorFunction = ts.factory.createFunctionExpression(
4773
+ void 0,
4774
+ ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
4775
+ void 0,
4776
+ originalNode.typeParameters,
4777
+ originalNode.parameters,
4778
+ returnTypeAnnotation,
4779
+ generatorFunction.body
4780
+ );
4781
+ let fnExpression = ts.factory.createPropertyAccessExpression(
4782
+ ts.factory.createIdentifier(effectModuleName),
4783
+ "fn"
4784
+ );
4785
+ if (traceName) {
4786
+ fnExpression = ts.factory.createCallExpression(
4787
+ fnExpression,
4788
+ void 0,
4789
+ [ts.factory.createStringLiteral(traceName)]
4790
+ );
4791
+ }
4792
+ const effectFnCall = ts.factory.createCallExpression(
4793
+ fnExpression,
4794
+ void 0,
4795
+ [newGeneratorFunction, ...pipeArguments2]
4796
+ );
4797
+ if (ts.isFunctionDeclaration(originalNode)) {
4798
+ return tsUtils.tryPreserveDeclarationSemantics(originalNode, effectFnCall, false);
4799
+ }
4800
+ return effectFnCall;
4801
+ };
4802
+ const createEffectFnUntracedNode = (originalNode, generatorFunction, effectModuleName, effectTypes, pipeArguments2) => {
4803
+ const returnTypeAnnotation = effectTypes ? createReturnTypeAnnotation(effectModuleName, effectTypes, originalNode) : void 0;
4804
+ const newGeneratorFunction = ts.factory.createFunctionExpression(
4805
+ void 0,
4806
+ ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
4807
+ void 0,
4808
+ originalNode.typeParameters,
4809
+ originalNode.parameters,
4810
+ returnTypeAnnotation,
4811
+ generatorFunction.body
4812
+ );
4813
+ const effectFnCall = ts.factory.createCallExpression(
4814
+ ts.factory.createPropertyAccessExpression(
4815
+ ts.factory.createIdentifier(effectModuleName),
4816
+ "fnUntraced"
4817
+ ),
4818
+ void 0,
4819
+ [newGeneratorFunction, ...pipeArguments2]
4820
+ );
4821
+ if (ts.isFunctionDeclaration(originalNode)) {
4822
+ return tsUtils.tryPreserveDeclarationSemantics(originalNode, effectFnCall, false);
4823
+ }
4824
+ return effectFnCall;
4825
+ };
4826
+ const nodeToVisit = [];
4827
+ const appendNodeToVisit = (node) => {
4828
+ nodeToVisit.push(node);
4829
+ return void 0;
4830
+ };
4831
+ ts.forEachChild(sourceFile, appendNodeToVisit);
4832
+ while (nodeToVisit.length > 0) {
4833
+ const node = nodeToVisit.shift();
4834
+ ts.forEachChild(node, appendNodeToVisit);
4835
+ const target = yield* pipe(
4836
+ parseEffectFnOpportunityTarget(node, sourceFile),
4837
+ option
4838
+ );
4839
+ if (isNone2(target)) continue;
4840
+ const {
4841
+ effectModuleName,
4842
+ effectTypes,
4843
+ generatorFunction,
4844
+ hasReturnTypeAnnotation,
4845
+ nameIdentifier,
4846
+ node: targetNode,
4847
+ pipeArguments: pipeArguments2,
4848
+ traceName
4849
+ } = target.value;
4850
+ const fixes = [];
4851
+ fixes.push({
4852
+ fixName: "effectFnOpportunity_toEffectFn",
4853
+ description: traceName ? `Convert to Effect.fn("${traceName}")` : "Convert to Effect.fn",
4854
+ apply: gen(function* () {
4855
+ const changeTracker = yield* service(ChangeTracker);
4856
+ const newNode = createEffectFnNode(
4857
+ targetNode,
4858
+ generatorFunction,
4859
+ effectModuleName,
4860
+ traceName,
4861
+ hasReturnTypeAnnotation ? effectTypes : void 0,
4862
+ pipeArguments2
4863
+ );
4864
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
4865
+ })
4866
+ });
4867
+ fixes.push({
4868
+ fixName: "effectFnOpportunity_toEffectFnUntraced",
4869
+ description: "Convert to Effect.fnUntraced",
4870
+ apply: gen(function* () {
4871
+ const changeTracker = yield* service(ChangeTracker);
4872
+ const newNode = createEffectFnUntracedNode(
4873
+ targetNode,
4874
+ generatorFunction,
4875
+ effectModuleName,
4876
+ hasReturnTypeAnnotation ? effectTypes : void 0,
4877
+ pipeArguments2
4878
+ );
4879
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
4880
+ })
4881
+ });
4882
+ report({
4883
+ location: nameIdentifier ?? targetNode,
4884
+ messageText: `This function could benefit from Effect.fn's automatic tracing and concise syntax, or Effect.fnUntraced to get just a more concise syntax.`,
4885
+ fixes
4886
+ });
4887
+ }
4888
+ })
4889
+ });
4890
+ function findSingleReturnStatement(ts, block) {
4891
+ if (block.statements.length !== 1) return void 0;
4892
+ const statement = block.statements[0];
4893
+ if (!ts.isReturnStatement(statement)) return void 0;
4894
+ return statement;
4895
+ }
4896
+ function getNameIdentifier(ts, node) {
4897
+ if (ts.isFunctionDeclaration(node) && node.name) {
4898
+ return node.name;
4899
+ }
4900
+ if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
4901
+ return node.parent.name;
4902
+ }
4903
+ if (node.parent && ts.isPropertyAssignment(node.parent)) {
4904
+ const name = node.parent.name;
4905
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
4906
+ return name;
4907
+ }
4908
+ }
4909
+ if (node.parent && ts.isPropertyDeclaration(node.parent)) {
4910
+ const name = node.parent.name;
4911
+ if (ts.isIdentifier(name)) {
4912
+ return name;
4913
+ }
4914
+ }
4915
+ return void 0;
4916
+ }
4917
+
4413
4918
  // src/diagnostics/effectGenUsesAdapter.ts
4414
4919
  var effectGenUsesAdapter = createDiagnostic({
4415
4920
  name: "effectGenUsesAdapter",
@@ -4497,6 +5002,70 @@ var effectInVoidSuccess = createDiagnostic({
4497
5002
  })
4498
5003
  });
4499
5004
 
5005
+ // src/diagnostics/effectMapVoid.ts
5006
+ var effectMapVoid = createDiagnostic({
5007
+ name: "effectMapVoid",
5008
+ code: 40,
5009
+ description: "Suggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {})",
5010
+ severity: "suggestion",
5011
+ apply: fn("effectMapVoid.apply")(function* (sourceFile, report) {
5012
+ const ts = yield* service(TypeScriptApi);
5013
+ const typeParser = yield* service(TypeParser);
5014
+ const tsUtils = yield* service(TypeScriptUtils);
5015
+ const nodeToVisit = [];
5016
+ const appendNodeToVisit = (node) => {
5017
+ nodeToVisit.push(node);
5018
+ return void 0;
5019
+ };
5020
+ ts.forEachChild(sourceFile, appendNodeToVisit);
5021
+ while (nodeToVisit.length > 0) {
5022
+ const node = nodeToVisit.shift();
5023
+ ts.forEachChild(node, appendNodeToVisit);
5024
+ if (ts.isCallExpression(node)) {
5025
+ const isMapCall = yield* pipe(
5026
+ typeParser.isNodeReferenceToEffectModuleApi("map")(node.expression),
5027
+ option
5028
+ );
5029
+ if (isSome2(isMapCall)) {
5030
+ const callback = node.arguments[0];
5031
+ if (!callback) continue;
5032
+ const match2 = yield* pipe(
5033
+ typeParser.emptyFunction(callback),
5034
+ orElse2(
5035
+ () => pipe(
5036
+ typeParser.lazyExpression(callback),
5037
+ flatMap2(
5038
+ (lazy) => tsUtils.isVoidExpression(lazy.expression) ? succeed(lazy) : typeParserIssue("Expression is not void")
5039
+ )
5040
+ )
5041
+ ),
5042
+ option
5043
+ );
5044
+ if (isNone2(match2)) continue;
5045
+ report({
5046
+ location: node.expression,
5047
+ messageText: "Effect.asVoid can be used instead to discard the success value",
5048
+ fixes: [{
5049
+ fixName: "effectMapVoid_fix",
5050
+ description: "Replace with Effect.asVoid",
5051
+ apply: gen(function* () {
5052
+ const changeTracker = yield* service(ChangeTracker);
5053
+ if (ts.isPropertyAccessExpression(node.expression)) {
5054
+ const newNode = ts.factory.createPropertyAccessExpression(
5055
+ node.expression.expression,
5056
+ ts.factory.createIdentifier("asVoid")
5057
+ );
5058
+ changeTracker.replaceNode(sourceFile, node, newNode);
5059
+ }
5060
+ })
5061
+ }]
5062
+ });
5063
+ }
5064
+ }
5065
+ }
5066
+ })
5067
+ });
5068
+
4500
5069
  // src/diagnostics/floatingEffect.ts
4501
5070
  var floatingEffect = createDiagnostic({
4502
5071
  name: "floatingEffect",
@@ -5105,75 +5674,91 @@ var missedPipeableOpportunity = createDiagnostic({
5105
5674
  apply: fn("missedPipeableOpportunity.apply")(function* (sourceFile, report) {
5106
5675
  const ts = yield* service(TypeScriptApi);
5107
5676
  const typeChecker = yield* service(TypeCheckerApi);
5108
- const typeCheckerUtils = yield* service(TypeCheckerUtils);
5109
5677
  const typeParser = yield* service(TypeParser);
5110
5678
  const options = yield* service(LanguageServicePluginOptions);
5111
- const nodeToVisit = [sourceFile];
5112
- const prependNodeToVisit = (node) => {
5113
- nodeToVisit.unshift(node);
5114
- return void 0;
5115
- };
5116
- const callChainNodes = /* @__PURE__ */ new WeakMap();
5117
- while (nodeToVisit.length > 0) {
5118
- const node = nodeToVisit.shift();
5119
- if (ts.isCallExpression(node) && node.arguments.length === 1) {
5120
- const isPipeCall = yield* pipe(typeParser.pipeCall(node), orElse2(() => void_));
5121
- if (!isPipeCall) {
5122
- const resolvedSignature = typeChecker.getResolvedSignature(node);
5123
- if (resolvedSignature) {
5124
- const returnType = typeChecker.getReturnTypeOfSignature(resolvedSignature);
5125
- if (returnType) {
5126
- const callSignatures = typeChecker.getSignaturesOfType(returnType, ts.SignatureKind.Call);
5127
- if (callSignatures.length === 0) {
5128
- const parentChain = callChainNodes.get(node) || [];
5129
- callChainNodes.set(node.arguments[0], parentChain.concat(node));
5130
- }
5679
+ const flows = yield* typeParser.pipingFlows(false)(sourceFile);
5680
+ for (const flow2 of flows) {
5681
+ if (flow2.transformations.length < options.pipeableMinArgCount) {
5682
+ continue;
5683
+ }
5684
+ const finalType = flow2.transformations[flow2.transformations.length - 1].outType;
5685
+ if (!finalType) {
5686
+ continue;
5687
+ }
5688
+ const callSigs = typeChecker.getSignaturesOfType(finalType, ts.SignatureKind.Call);
5689
+ if (callSigs.length > 0) {
5690
+ continue;
5691
+ }
5692
+ let firstPipeableIndex = -1;
5693
+ const subjectType = flow2.subject.outType;
5694
+ if (!subjectType) {
5695
+ continue;
5696
+ }
5697
+ const subjectIsPipeable = yield* pipe(
5698
+ typeParser.pipeableType(subjectType, flow2.subject.node),
5699
+ option
5700
+ );
5701
+ if (subjectIsPipeable._tag === "Some") {
5702
+ firstPipeableIndex = 0;
5703
+ } else {
5704
+ for (let i = 0; i < flow2.transformations.length; i++) {
5705
+ const t = flow2.transformations[i];
5706
+ if (t.outType) {
5707
+ const isPipeable = yield* pipe(
5708
+ typeParser.pipeableType(t.outType, flow2.node),
5709
+ option
5710
+ );
5711
+ if (isPipeable._tag === "Some") {
5712
+ firstPipeableIndex = i + 1;
5713
+ break;
5131
5714
  }
5132
5715
  }
5133
5716
  }
5134
- } else if (callChainNodes.has(node) && ts.isExpression(node)) {
5135
- const parentChain = (callChainNodes.get(node) || []).slice();
5136
- const originalParentChain = parentChain.slice();
5137
- while (parentChain.length > options.pipeableMinArgCount) {
5138
- const subject = parentChain.pop();
5139
- const resultType = typeCheckerUtils.getTypeAtLocation(subject);
5140
- if (!resultType) continue;
5141
- const pipeableType = yield* pipe(typeParser.pipeableType(resultType, subject), orElse2(() => void_));
5142
- if (pipeableType) {
5143
- report({
5144
- location: parentChain[0],
5145
- messageText: `Nested function calls can be converted to pipeable style for better readability.`,
5146
- fixes: [{
5147
- fixName: "missedPipeableOpportunity_fix",
5148
- description: "Convert to pipe style",
5149
- apply: gen(function* () {
5150
- const changeTracker = yield* service(ChangeTracker);
5151
- changeTracker.replaceNode(
5152
- sourceFile,
5153
- parentChain[0],
5154
- ts.factory.createCallExpression(
5155
- ts.factory.createPropertyAccessExpression(
5156
- subject,
5157
- "pipe"
5158
- ),
5159
- void 0,
5160
- pipe(
5161
- parentChain,
5162
- filter(ts.isCallExpression),
5163
- map3((call) => call.expression),
5164
- reverse
5165
- )
5166
- )
5167
- );
5168
- })
5169
- }]
5170
- });
5171
- originalParentChain.forEach((node2) => callChainNodes.delete(node2));
5172
- break;
5173
- }
5174
- }
5175
5717
  }
5176
- ts.forEachChild(node, prependNodeToVisit);
5718
+ if (firstPipeableIndex === -1) {
5719
+ continue;
5720
+ }
5721
+ const transformationsAfterPipeable = flow2.transformations.slice(firstPipeableIndex);
5722
+ const callKindCount = transformationsAfterPipeable.filter((t) => t.kind === "call").length;
5723
+ if (callKindCount < options.pipeableMinArgCount) {
5724
+ continue;
5725
+ }
5726
+ const pipeableSubjectNode = firstPipeableIndex === 0 ? flow2.subject.node : typeParser.reconstructPipingFlow({
5727
+ subject: flow2.subject,
5728
+ transformations: flow2.transformations.slice(0, firstPipeableIndex)
5729
+ });
5730
+ const pipeableTransformations = flow2.transformations.slice(firstPipeableIndex);
5731
+ report({
5732
+ location: flow2.node,
5733
+ messageText: `Nested function calls can be converted to pipeable style for better readability.`,
5734
+ fixes: [{
5735
+ fixName: "missedPipeableOpportunity_fix",
5736
+ description: "Convert to pipe style",
5737
+ apply: gen(function* () {
5738
+ const changeTracker = yield* service(ChangeTracker);
5739
+ const pipeArgs = pipeableTransformations.map((t) => {
5740
+ if (t.args) {
5741
+ return ts.factory.createCallExpression(
5742
+ t.callee,
5743
+ void 0,
5744
+ t.args
5745
+ );
5746
+ } else {
5747
+ return t.callee;
5748
+ }
5749
+ });
5750
+ const newNode = ts.factory.createCallExpression(
5751
+ ts.factory.createPropertyAccessExpression(
5752
+ pipeableSubjectNode,
5753
+ "pipe"
5754
+ ),
5755
+ void 0,
5756
+ pipeArgs
5757
+ );
5758
+ changeTracker.replaceNode(sourceFile, flow2.node, newNode);
5759
+ })
5760
+ }]
5761
+ });
5177
5762
  }
5178
5763
  })
5179
5764
  });
@@ -5672,28 +6257,34 @@ var multipleEffectProvide = createDiagnostic({
5672
6257
  "effect",
5673
6258
  "Layer"
5674
6259
  ) || "Layer";
5675
- const parseEffectProvideLayer = (node) => {
5676
- if (ts.isCallExpression(node) && node.arguments.length > 0) {
5677
- const layer = node.arguments[0];
5678
- const type = typeCheckerUtils.getTypeAtLocation(layer);
5679
- if (!type) return void_;
5680
- return pipe(
5681
- typeParser.isNodeReferenceToEffectModuleApi("provide")(node.expression),
5682
- flatMap2(() => typeParser.layerType(type, layer)),
5683
- map4(() => ({ layer, node })),
5684
- orElse2(() => void_)
5685
- );
5686
- }
5687
- return void_;
5688
- };
5689
- const parsePipeCall = (node) => gen(function* () {
5690
- const { args: args2 } = yield* typeParser.pipeCall(node);
6260
+ const flows = yield* typeParser.pipingFlows(true)(sourceFile);
6261
+ for (const flow2 of flows) {
5691
6262
  let currentChunk = 0;
5692
6263
  const previousLayers = [[]];
5693
- for (const pipeArg of args2) {
5694
- const parsedProvide = yield* parseEffectProvideLayer(pipeArg);
5695
- if (parsedProvide) {
5696
- previousLayers[currentChunk].push(parsedProvide);
6264
+ for (const transformation of flow2.transformations) {
6265
+ if (!transformation.args || transformation.args.length === 0) {
6266
+ currentChunk++;
6267
+ previousLayers.push([]);
6268
+ continue;
6269
+ }
6270
+ const isProvideCall = yield* pipe(
6271
+ typeParser.isNodeReferenceToEffectModuleApi("provide")(transformation.callee),
6272
+ option
6273
+ );
6274
+ if (isSome2(isProvideCall)) {
6275
+ const layer = transformation.args[0];
6276
+ const type = typeCheckerUtils.getTypeAtLocation(layer);
6277
+ const node = ts.findAncestor(transformation.callee, ts.isCallExpression);
6278
+ const isLayerType = type ? yield* pipe(
6279
+ typeParser.layerType(type, layer),
6280
+ option
6281
+ ) : none2();
6282
+ if (isSome2(isLayerType) && node) {
6283
+ previousLayers[currentChunk].push({ layer, node });
6284
+ } else {
6285
+ currentChunk++;
6286
+ previousLayers.push([]);
6287
+ }
5697
6288
  } else {
5698
6289
  currentChunk++;
5699
6290
  previousLayers.push([]);
@@ -5733,19 +6324,6 @@ var multipleEffectProvide = createDiagnostic({
5733
6324
  }]
5734
6325
  });
5735
6326
  }
5736
- });
5737
- const nodeToVisit = [];
5738
- const appendNodeToVisit = (node) => {
5739
- nodeToVisit.push(node);
5740
- return void 0;
5741
- };
5742
- ts.forEachChild(sourceFile, appendNodeToVisit);
5743
- while (nodeToVisit.length > 0) {
5744
- const node = nodeToVisit.shift();
5745
- ts.forEachChild(node, appendNodeToVisit);
5746
- if (ts.isCallExpression(node)) {
5747
- yield* pipe(parsePipeCall(node), ignore);
5748
- }
5749
6327
  }
5750
6328
  })
5751
6329
  });
@@ -7059,6 +7637,56 @@ var overriddenSchemaConstructor = createDiagnostic({
7059
7637
  })
7060
7638
  });
7061
7639
 
7640
+ // src/diagnostics/redundantSchemaTagIdentifier.ts
7641
+ var redundantSchemaTagIdentifier = createDiagnostic({
7642
+ name: "redundantSchemaTagIdentifier",
7643
+ code: 42,
7644
+ description: "Suggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest",
7645
+ severity: "suggestion",
7646
+ apply: fn("redundantSchemaTagIdentifier.apply")(function* (sourceFile, report) {
7647
+ const ts = yield* service(TypeScriptApi);
7648
+ const typeParser = yield* service(TypeParser);
7649
+ const nodeToVisit = [];
7650
+ const appendNodeToVisit = (node) => {
7651
+ nodeToVisit.push(node);
7652
+ return void 0;
7653
+ };
7654
+ ts.forEachChild(sourceFile, appendNodeToVisit);
7655
+ while (nodeToVisit.length > 0) {
7656
+ const node = nodeToVisit.shift();
7657
+ if (ts.isClassDeclaration(node) && node.name && node.heritageClauses) {
7658
+ const result = yield* pipe(
7659
+ typeParser.extendsSchemaTaggedClass(node),
7660
+ orElse2(() => typeParser.extendsSchemaTaggedError(node)),
7661
+ orElse2(() => typeParser.extendsSchemaTaggedRequest(node)),
7662
+ orElse2(() => void_)
7663
+ );
7664
+ if (result && result.keyStringLiteral && result.tagStringLiteral) {
7665
+ const { keyStringLiteral, tagStringLiteral } = result;
7666
+ if (keyStringLiteral.text === tagStringLiteral.text) {
7667
+ report({
7668
+ location: keyStringLiteral,
7669
+ messageText: `Identifier '${keyStringLiteral.text}' is redundant since it equals the _tag value`,
7670
+ fixes: [{
7671
+ fixName: "redundantSchemaTagIdentifier_removeIdentifier",
7672
+ description: `Remove redundant identifier '${keyStringLiteral.text}'`,
7673
+ apply: gen(function* () {
7674
+ const changeTracker = yield* service(ChangeTracker);
7675
+ changeTracker.deleteRange(sourceFile, {
7676
+ pos: ts.getTokenPosOfNode(keyStringLiteral, sourceFile),
7677
+ end: keyStringLiteral.end
7678
+ });
7679
+ })
7680
+ }]
7681
+ });
7682
+ }
7683
+ }
7684
+ }
7685
+ ts.forEachChild(node, appendNodeToVisit);
7686
+ }
7687
+ })
7688
+ });
7689
+
7062
7690
  // src/diagnostics/returnEffectInGen.ts
7063
7691
  var returnEffectInGen = createDiagnostic({
7064
7692
  name: "returnEffectInGen",
@@ -8059,7 +8687,10 @@ var diagnostics = [
8059
8687
  schemaStructWithTag,
8060
8688
  globalErrorInEffectCatch,
8061
8689
  globalErrorInEffectFailure,
8062
- layerMergeAllWithDependencies
8690
+ layerMergeAllWithDependencies,
8691
+ effectMapVoid,
8692
+ effectFnOpportunity,
8693
+ redundantSchemaTagIdentifier
8063
8694
  ];
8064
8695
 
8065
8696
  // src/effect-lsp-patch-utils.ts
@@ -8106,12 +8737,12 @@ function checkSourceFileWorker(tsInstance, program, sourceFile, compilerOptions,
8106
8737
  map((_) => _.diagnostics),
8107
8738
  map(
8108
8739
  filter(
8109
- (_) => _.category === tsInstance.DiagnosticCategory.Error || _.category === tsInstance.DiagnosticCategory.Warning || moduleName === "tsc" && parsedOptions.reportSuggestionsAsWarningsInTsc && (_.category === tsInstance.DiagnosticCategory.Suggestion || _.category === tsInstance.DiagnosticCategory.Message)
8740
+ (_) => _.category === tsInstance.DiagnosticCategory.Error || _.category === tsInstance.DiagnosticCategory.Warning || moduleName === "tsc" && parsedOptions.includeSuggestionsInTsc && (_.category === tsInstance.DiagnosticCategory.Message || _.category === tsInstance.DiagnosticCategory.Suggestion)
8110
8741
  )
8111
8742
  ),
8112
8743
  map(
8113
8744
  map3((_) => {
8114
- if (moduleName === "tsc" && parsedOptions.reportSuggestionsAsWarningsInTsc && _.category === tsInstance.DiagnosticCategory.Suggestion) {
8745
+ if (moduleName === "tsc" && parsedOptions.includeSuggestionsInTsc && _.category === tsInstance.DiagnosticCategory.Suggestion) {
8115
8746
  return { ..._, category: tsInstance.DiagnosticCategory.Message };
8116
8747
  }
8117
8748
  return _;