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