@effect/language-service 0.65.0 → 0.67.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.
@@ -3095,6 +3095,97 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3095
3095
  "TypeParser.effectFnGen",
3096
3096
  (node) => node
3097
3097
  );
3098
+ const findEnclosingScopes = fn("TypeParser.findEnclosingScopes")(function* (startNode) {
3099
+ let currentParent = startNode.parent;
3100
+ let scopeNode = void 0;
3101
+ let effectGenResult = void 0;
3102
+ while (currentParent) {
3103
+ const nodeToCheck = currentParent;
3104
+ if (!scopeNode) {
3105
+ if (ts.isFunctionExpression(nodeToCheck) || ts.isFunctionDeclaration(nodeToCheck) || ts.isMethodDeclaration(nodeToCheck) || ts.isArrowFunction(nodeToCheck) || ts.isGetAccessorDeclaration(nodeToCheck) || ts.isSetAccessorDeclaration(nodeToCheck)) {
3106
+ scopeNode = nodeToCheck;
3107
+ }
3108
+ }
3109
+ if (!effectGenResult) {
3110
+ const isEffectGen = yield* pipe(
3111
+ effectGen(nodeToCheck),
3112
+ map4((result) => ({
3113
+ node: result.node,
3114
+ effectModule: result.effectModule,
3115
+ generatorFunction: result.generatorFunction,
3116
+ body: result.body
3117
+ })),
3118
+ orElse2(
3119
+ () => pipe(
3120
+ effectFnUntracedGen(nodeToCheck),
3121
+ map4((result) => ({
3122
+ node: result.node,
3123
+ effectModule: result.effectModule,
3124
+ generatorFunction: result.generatorFunction,
3125
+ body: result.body,
3126
+ pipeArguments: result.pipeArguments
3127
+ }))
3128
+ )
3129
+ ),
3130
+ orElse2(
3131
+ () => pipe(
3132
+ effectFnGen(nodeToCheck),
3133
+ map4((result) => ({
3134
+ node: result.node,
3135
+ effectModule: result.effectModule,
3136
+ generatorFunction: result.generatorFunction,
3137
+ body: result.body,
3138
+ pipeArguments: result.pipeArguments
3139
+ }))
3140
+ )
3141
+ ),
3142
+ option
3143
+ );
3144
+ if (isSome2(isEffectGen)) {
3145
+ effectGenResult = isEffectGen.value;
3146
+ }
3147
+ }
3148
+ if (scopeNode && effectGenResult) {
3149
+ break;
3150
+ }
3151
+ currentParent = nodeToCheck.parent;
3152
+ }
3153
+ return { scopeNode, effectGen: effectGenResult };
3154
+ });
3155
+ const effectFn = cachedBy(
3156
+ function(node) {
3157
+ if (!ts.isCallExpression(node)) {
3158
+ return typeParserIssue("Node is not a call expression", void 0, node);
3159
+ }
3160
+ if (node.arguments.length === 0) {
3161
+ return typeParserIssue("Node has no arguments", void 0, node);
3162
+ }
3163
+ const regularFunction = node.arguments[0];
3164
+ if (!ts.isFunctionExpression(regularFunction) && !ts.isArrowFunction(regularFunction)) {
3165
+ return typeParserIssue("Node is not a function expression or arrow function", void 0, node);
3166
+ }
3167
+ if (ts.isFunctionExpression(regularFunction) && regularFunction.asteriskToken !== void 0) {
3168
+ return typeParserIssue("Node is a generator function, not a regular function", void 0, node);
3169
+ }
3170
+ const expressionToTest = ts.isCallExpression(node.expression) ? node.expression.expression : node.expression;
3171
+ if (!ts.isPropertyAccessExpression(expressionToTest)) {
3172
+ return typeParserIssue("Node is not a property access expression", void 0, node);
3173
+ }
3174
+ const propertyAccess = expressionToTest;
3175
+ const pipeArguments2 = node.arguments.slice(1);
3176
+ return pipe(
3177
+ isNodeReferenceToEffectModuleApi("fn")(propertyAccess),
3178
+ map4(() => ({
3179
+ node,
3180
+ effectModule: propertyAccess.expression,
3181
+ regularFunction,
3182
+ pipeArguments: pipeArguments2
3183
+ }))
3184
+ );
3185
+ },
3186
+ "TypeParser.effectFn",
3187
+ (node) => node
3188
+ );
3098
3189
  const unnecessaryEffectGen2 = cachedBy(
3099
3190
  fn("TypeParser.unnecessaryEffectGen")(function* (node) {
3100
3191
  const { body } = yield* effectGen(node);
@@ -3206,6 +3297,28 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3206
3297
  `TypeParser.isNodeReferenceToEffectSchemaModuleApi(${memberName})`,
3207
3298
  (node) => node
3208
3299
  );
3300
+ const isEffectParseResultSourceFile = cachedBy(
3301
+ fn("TypeParser.isEffectParseResultSourceFile")(function* (sourceFile) {
3302
+ const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
3303
+ if (!moduleSymbol) return yield* typeParserIssue("Node has no symbol", void 0, sourceFile);
3304
+ const parseIssueSymbol = typeChecker.tryGetMemberInModuleExports("ParseIssue", moduleSymbol);
3305
+ if (!parseIssueSymbol) return yield* typeParserIssue("ParseIssue type not found", void 0, sourceFile);
3306
+ const decodeSyncSymbol = typeChecker.tryGetMemberInModuleExports("decodeSync", moduleSymbol);
3307
+ if (!decodeSyncSymbol) return yield* typeParserIssue("decodeSync not found", void 0, sourceFile);
3308
+ const encodeSyncSymbol = typeChecker.tryGetMemberInModuleExports("encodeSync", moduleSymbol);
3309
+ if (!encodeSyncSymbol) return yield* typeParserIssue("encodeSync not found", void 0, sourceFile);
3310
+ return sourceFile;
3311
+ }),
3312
+ "TypeParser.isEffectParseResultSourceFile",
3313
+ (sourceFile) => sourceFile
3314
+ );
3315
+ const isNodeReferenceToEffectParseResultModuleApi = (memberName) => cachedBy(
3316
+ fn("TypeParser.isNodeReferenceToEffectParseResultModuleApi")(function* (node) {
3317
+ return yield* isNodeReferenceToExportOfPackageModule(node, "effect", isEffectParseResultSourceFile, memberName);
3318
+ }),
3319
+ `TypeParser.isNodeReferenceToEffectParseResultModuleApi(${memberName})`,
3320
+ (node) => node
3321
+ );
3209
3322
  const contextTagVarianceStruct = (type, atLocation) => map4(
3210
3323
  all(
3211
3324
  varianceStructInvariantType(type, atLocation, "_Identifier"),
@@ -3986,11 +4099,65 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
3986
4099
  if (includeEffectFn) {
3987
4100
  const effectFnGenParsed = yield* pipe(effectFnGen(node), option);
3988
4101
  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;
4102
+ const effectFnNonGenParsed = isNone2(effectFnGenParsed) && isNone2(effectFnUntracedGenParsed) ? yield* pipe(effectFn(node), option) : none2();
4103
+ const isEffectFnGen = isSome2(effectFnGenParsed);
4104
+ const isEffectFnUntracedGen = isSome2(effectFnUntracedGenParsed);
4105
+ const isEffectFnNonGen = isSome2(effectFnNonGenParsed);
4106
+ const transformationKind = isEffectFnUntracedGen ? "effectFnUntraced" : "effectFn";
4107
+ if (isEffectFnGen || isEffectFnUntracedGen) {
4108
+ const effectFnParsed = isEffectFnGen ? effectFnGenParsed : effectFnUntracedGenParsed;
4109
+ if (isSome2(effectFnParsed) && effectFnParsed.value.pipeArguments.length > 0) {
4110
+ const fnResult = effectFnParsed.value;
4111
+ const pipeArgs = fnResult.pipeArguments;
4112
+ const transformations = [];
4113
+ let subjectType;
4114
+ for (let i = 0; i < pipeArgs.length; i++) {
4115
+ const arg = pipeArgs[i];
4116
+ const contextualType = typeChecker.getContextualType(arg);
4117
+ const callSigs = contextualType ? typeChecker.getSignaturesOfType(contextualType, ts.SignatureKind.Call) : [];
4118
+ const outType = callSigs.length > 0 ? typeChecker.getReturnTypeOfSignature(callSigs[0]) : void 0;
4119
+ if (i === 0 && callSigs.length > 0) {
4120
+ const params = callSigs[0].parameters;
4121
+ if (params.length > 0) {
4122
+ subjectType = typeChecker.getTypeOfSymbol(params[0]);
4123
+ }
4124
+ }
4125
+ if (ts.isCallExpression(arg)) {
4126
+ transformations.push({
4127
+ callee: arg.expression,
4128
+ args: Array.from(arg.arguments),
4129
+ outType,
4130
+ kind: transformationKind
4131
+ });
4132
+ } else {
4133
+ transformations.push({
4134
+ callee: arg,
4135
+ args: void 0,
4136
+ outType,
4137
+ kind: transformationKind
4138
+ });
4139
+ }
4140
+ }
4141
+ const newFlow = {
4142
+ node,
4143
+ subject: {
4144
+ node,
4145
+ outType: subjectType
4146
+ },
4147
+ transformations
4148
+ };
4149
+ result.push(newFlow);
4150
+ workQueue.push([fnResult.body, void 0]);
4151
+ for (const arg of pipeArgs) {
4152
+ ts.forEachChild(arg, (c) => {
4153
+ workQueue.push([c, void 0]);
4154
+ });
4155
+ }
4156
+ continue;
4157
+ }
4158
+ }
4159
+ if (isEffectFnNonGen && isSome2(effectFnNonGenParsed) && effectFnNonGenParsed.value.pipeArguments.length > 0) {
4160
+ const fnResult = effectFnNonGenParsed.value;
3994
4161
  const pipeArgs = fnResult.pipeArguments;
3995
4162
  const transformations = [];
3996
4163
  let subjectType;
@@ -4010,14 +4177,14 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
4010
4177
  callee: arg.expression,
4011
4178
  args: Array.from(arg.arguments),
4012
4179
  outType,
4013
- kind: transformationKind
4180
+ kind: "effectFn"
4014
4181
  });
4015
4182
  } else {
4016
4183
  transformations.push({
4017
4184
  callee: arg,
4018
4185
  args: void 0,
4019
4186
  outType,
4020
- kind: transformationKind
4187
+ kind: "effectFn"
4021
4188
  });
4022
4189
  }
4023
4190
  }
@@ -4030,7 +4197,16 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
4030
4197
  transformations
4031
4198
  };
4032
4199
  result.push(newFlow);
4033
- workQueue.push([fnResult.body, void 0]);
4200
+ const regularFn = fnResult.regularFunction;
4201
+ if (ts.isArrowFunction(regularFn)) {
4202
+ if (ts.isBlock(regularFn.body)) {
4203
+ workQueue.push([regularFn.body, void 0]);
4204
+ } else {
4205
+ workQueue.push([regularFn.body, void 0]);
4206
+ }
4207
+ } else if (regularFn.body) {
4208
+ workQueue.push([regularFn.body, void 0]);
4209
+ }
4034
4210
  for (const arg of pipeArgs) {
4035
4211
  ts.forEachChild(arg, (c) => {
4036
4212
  workQueue.push([c, void 0]);
@@ -4093,6 +4269,7 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
4093
4269
  return {
4094
4270
  isNodeReferenceToEffectModuleApi,
4095
4271
  isNodeReferenceToEffectSchemaModuleApi,
4272
+ isNodeReferenceToEffectParseResultModuleApi,
4096
4273
  isNodeReferenceToEffectDataModuleApi,
4097
4274
  isNodeReferenceToEffectContextModuleApi,
4098
4275
  isNodeReferenceToEffectSqlModelModuleApi,
@@ -4106,6 +4283,8 @@ function make2(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
4106
4283
  effectGen,
4107
4284
  effectFnUntracedGen,
4108
4285
  effectFnGen,
4286
+ findEnclosingScopes,
4287
+ effectFn,
4109
4288
  extendsCauseYieldableError,
4110
4289
  unnecessaryEffectGen: unnecessaryEffectGen2,
4111
4290
  effectSchemaType,
@@ -4677,106 +4856,195 @@ ${versions.map((version) => `- found ${version} at ${resolvedPackages[packageNam
4677
4856
  });
4678
4857
 
4679
4858
  // 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
4859
  var effectFnOpportunity = createDiagnostic({
4745
4860
  name: "effectFnOpportunity",
4746
4861
  code: 41,
4747
- description: "Suggests using Effect.fn for functions that return Effect.gen",
4862
+ description: "Suggests using Effect.fn for functions that returns an Effect",
4748
4863
  severity: "suggestion",
4749
4864
  apply: fn("effectFnOpportunity.apply")(function* (sourceFile, report) {
4750
4865
  const ts = yield* service(TypeScriptApi);
4751
4866
  const typeChecker = yield* service(TypeCheckerApi);
4867
+ const typeCheckerUtils = yield* service(TypeCheckerUtils);
4868
+ const typeParser = yield* service(TypeParser);
4752
4869
  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]
4870
+ const sourceEffectModuleName = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
4871
+ sourceFile,
4872
+ "effect",
4873
+ "Effect"
4874
+ ) || "Effect";
4875
+ const findSingleReturnStatement = (block) => {
4876
+ if (block.statements.length !== 1) return void 0;
4877
+ const statement = block.statements[0];
4878
+ if (!ts.isReturnStatement(statement)) return void 0;
4879
+ return statement;
4880
+ };
4881
+ const getBodyExpression = (fnNode) => {
4882
+ if (ts.isArrowFunction(fnNode)) {
4883
+ if (ts.isBlock(fnNode.body)) {
4884
+ return findSingleReturnStatement(fnNode.body)?.expression;
4885
+ }
4886
+ return fnNode.body;
4887
+ } else if ((ts.isFunctionExpression(fnNode) || ts.isFunctionDeclaration(fnNode)) && fnNode.body) {
4888
+ return findSingleReturnStatement(fnNode.body)?.expression;
4889
+ }
4890
+ return void 0;
4891
+ };
4892
+ const getNameIdentifier = (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
+ const areParametersReferencedIn = (fnNode, nodes) => {
4914
+ if (fnNode.parameters.length === 0 || nodes.length === 0) return false;
4915
+ const firstParam = fnNode.parameters[0];
4916
+ const lastParam = fnNode.parameters[fnNode.parameters.length - 1];
4917
+ const paramsStart = firstParam.pos;
4918
+ const paramsEnd = lastParam.end;
4919
+ const isSymbolDeclaredInParams = (symbol3) => {
4920
+ const declarations = symbol3.declarations;
4921
+ if (!declarations) return false;
4922
+ return declarations.some((decl) => decl.pos >= paramsStart && decl.end <= paramsEnd);
4923
+ };
4924
+ const nodesToVisit = [...nodes];
4925
+ while (nodesToVisit.length > 0) {
4926
+ const node = nodesToVisit.shift();
4927
+ if (ts.isIdentifier(node)) {
4928
+ const symbol3 = typeChecker.getSymbolAtLocation(node);
4929
+ if (symbol3 && isSymbolDeclaredInParams(symbol3)) {
4930
+ return true;
4931
+ }
4932
+ }
4933
+ if (ts.isShorthandPropertyAssignment(node)) {
4934
+ const valueSymbol = typeChecker.getShorthandAssignmentValueSymbol(node);
4935
+ if (valueSymbol && isSymbolDeclaredInParams(valueSymbol)) {
4936
+ return true;
4937
+ }
4938
+ }
4939
+ ts.forEachChild(node, (child) => {
4940
+ nodesToVisit.push(child);
4941
+ return void 0;
4942
+ });
4943
+ }
4944
+ return false;
4945
+ };
4946
+ const tryParseGenOpportunity = (fnNode) => gen(function* () {
4947
+ const bodyExpression = getBodyExpression(fnNode);
4948
+ if (!bodyExpression) return yield* TypeParserIssue.issue;
4949
+ const { pipeArguments: pipeArguments2, subject } = yield* pipe(
4950
+ typeParser.pipeCall(bodyExpression),
4951
+ map4(({ args: args2, subject: subject2 }) => ({ subject: subject2, pipeArguments: args2 })),
4952
+ orElse2(() => succeed({ subject: bodyExpression, pipeArguments: [] }))
4953
+ );
4954
+ const { effectModule, generatorFunction } = yield* typeParser.effectGen(subject);
4955
+ const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
4956
+ return { effectModuleName, generatorFunction, pipeArguments: pipeArguments2 };
4957
+ });
4958
+ const isInsideEffectFn = (fnNode) => {
4959
+ const parent = fnNode.parent;
4960
+ if (!parent || !ts.isCallExpression(parent)) {
4961
+ return succeed(false);
4962
+ }
4963
+ if (parent.arguments[0] !== fnNode) {
4964
+ return succeed(false);
4965
+ }
4966
+ return pipe(
4967
+ typeParser.effectFn(parent),
4968
+ orElse2(() => typeParser.effectFnGen(parent)),
4969
+ orElse2(() => typeParser.effectFnUntracedGen(parent)),
4970
+ map4(() => true),
4971
+ orElse2(() => succeed(false))
4768
4972
  );
4769
4973
  };
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(
4974
+ const parseEffectFnOpportunityTarget = (node) => gen(function* () {
4975
+ if (!ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isFunctionDeclaration(node)) {
4976
+ return yield* TypeParserIssue.issue;
4977
+ }
4978
+ if ((ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) && node.asteriskToken) {
4979
+ return yield* TypeParserIssue.issue;
4980
+ }
4981
+ if (ts.isFunctionExpression(node) && node.name) {
4982
+ return yield* TypeParserIssue.issue;
4983
+ }
4984
+ if (yield* isInsideEffectFn(node)) {
4985
+ return yield* TypeParserIssue.issue;
4986
+ }
4987
+ const functionType = typeChecker.getTypeAtLocation(node);
4988
+ if (!functionType) return yield* TypeParserIssue.issue;
4989
+ const callSignatures = typeChecker.getSignaturesOfType(functionType, ts.SignatureKind.Call);
4990
+ if (callSignatures.length !== 1) return yield* TypeParserIssue.issue;
4991
+ const signature = callSignatures[0];
4992
+ const returnType = typeChecker.getReturnTypeOfSignature(signature);
4993
+ const unionMembers = typeCheckerUtils.unrollUnionMembers(returnType);
4994
+ yield* all(...unionMembers.map((member) => typeParser.strictEffectType(member, node)));
4995
+ const nameIdentifier = getNameIdentifier(node);
4996
+ const traceName = nameIdentifier ? ts.isIdentifier(nameIdentifier) ? ts.idText(nameIdentifier) : nameIdentifier.text : void 0;
4997
+ if (!traceName) return yield* TypeParserIssue.issue;
4998
+ const opportunity = yield* pipe(
4999
+ tryParseGenOpportunity(node),
5000
+ orElse2(() => {
5001
+ if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) {
5002
+ return TypeParserIssue.issue;
5003
+ }
5004
+ const body = ts.isArrowFunction(node) ? node.body : node.body;
5005
+ if (!body || !ts.isBlock(body) || body.statements.length <= 5) {
5006
+ return TypeParserIssue.issue;
5007
+ }
5008
+ return succeed({
5009
+ effectModuleName: sourceEffectModuleName,
5010
+ pipeArguments: [],
5011
+ generatorFunction: void 0
5012
+ });
5013
+ })
5014
+ );
5015
+ return {
5016
+ node,
5017
+ nameIdentifier,
5018
+ effectModuleName: opportunity.effectModuleName,
5019
+ traceName,
5020
+ pipeArguments: opportunity.pipeArguments,
5021
+ generatorFunction: opportunity.generatorFunction,
5022
+ hasParamsInPipeArgs: areParametersReferencedIn(node, opportunity.pipeArguments)
5023
+ };
5024
+ });
5025
+ const getFunctionBodyBlock = (node) => {
5026
+ if (ts.isArrowFunction(node)) {
5027
+ if (ts.isBlock(node.body)) {
5028
+ return node.body;
5029
+ }
5030
+ return ts.factory.createBlock([ts.factory.createReturnStatement(node.body)], true);
5031
+ }
5032
+ return node.body;
5033
+ };
5034
+ const isGeneratorFunction = (node) => {
5035
+ if (ts.isArrowFunction(node)) return false;
5036
+ return node.asteriskToken !== void 0;
5037
+ };
5038
+ const createEffectFnNode = (originalNode, innerFunction, effectModuleName, traceName, pipeArguments2) => {
5039
+ const isGenerator = isGeneratorFunction(innerFunction);
5040
+ const newFunction = ts.factory.createFunctionExpression(
4773
5041
  void 0,
4774
- ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
5042
+ isGenerator ? ts.factory.createToken(ts.SyntaxKind.AsteriskToken) : void 0,
4775
5043
  void 0,
4776
5044
  originalNode.typeParameters,
4777
5045
  originalNode.parameters,
4778
- returnTypeAnnotation,
4779
- generatorFunction.body
5046
+ void 0,
5047
+ getFunctionBodyBlock(innerFunction)
4780
5048
  );
4781
5049
  let fnExpression = ts.factory.createPropertyAccessExpression(
4782
5050
  ts.factory.createIdentifier(effectModuleName),
@@ -4789,34 +5057,27 @@ var effectFnOpportunity = createDiagnostic({
4789
5057
  [ts.factory.createStringLiteral(traceName)]
4790
5058
  );
4791
5059
  }
4792
- const effectFnCall = ts.factory.createCallExpression(
4793
- fnExpression,
4794
- void 0,
4795
- [newGeneratorFunction, ...pipeArguments2]
4796
- );
5060
+ const effectFnCall = ts.factory.createCallExpression(fnExpression, void 0, [newFunction, ...pipeArguments2]);
4797
5061
  if (ts.isFunctionDeclaration(originalNode)) {
4798
5062
  return tsUtils.tryPreserveDeclarationSemantics(originalNode, effectFnCall, false);
4799
5063
  }
4800
5064
  return effectFnCall;
4801
5065
  };
4802
- const createEffectFnUntracedNode = (originalNode, generatorFunction, effectModuleName, effectTypes, pipeArguments2) => {
4803
- const returnTypeAnnotation = effectTypes ? createReturnTypeAnnotation(effectModuleName, effectTypes, originalNode) : void 0;
4804
- const newGeneratorFunction = ts.factory.createFunctionExpression(
5066
+ const createEffectFnUntracedNode = (originalNode, innerFunction, effectModuleName, pipeArguments2) => {
5067
+ const isGenerator = isGeneratorFunction(innerFunction);
5068
+ const newFunction = ts.factory.createFunctionExpression(
4805
5069
  void 0,
4806
- ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
5070
+ isGenerator ? ts.factory.createToken(ts.SyntaxKind.AsteriskToken) : void 0,
4807
5071
  void 0,
4808
5072
  originalNode.typeParameters,
4809
5073
  originalNode.parameters,
4810
- returnTypeAnnotation,
4811
- generatorFunction.body
5074
+ void 0,
5075
+ getFunctionBodyBlock(innerFunction)
4812
5076
  );
4813
5077
  const effectFnCall = ts.factory.createCallExpression(
4814
- ts.factory.createPropertyAccessExpression(
4815
- ts.factory.createIdentifier(effectModuleName),
4816
- "fnUntraced"
4817
- ),
5078
+ ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(effectModuleName), "fnUntraced"),
4818
5079
  void 0,
4819
- [newGeneratorFunction, ...pipeArguments2]
5080
+ [newFunction, ...pipeArguments2]
4820
5081
  );
4821
5082
  if (ts.isFunctionDeclaration(originalNode)) {
4822
5083
  return tsUtils.tryPreserveDeclarationSemantics(originalNode, effectFnCall, false);
@@ -4832,88 +5093,41 @@ var effectFnOpportunity = createDiagnostic({
4832
5093
  while (nodeToVisit.length > 0) {
4833
5094
  const node = nodeToVisit.shift();
4834
5095
  ts.forEachChild(node, appendNodeToVisit);
4835
- const target = yield* pipe(
4836
- parseEffectFnOpportunityTarget(node, sourceFile),
4837
- option
4838
- );
5096
+ const target = yield* pipe(parseEffectFnOpportunityTarget(node), option);
4839
5097
  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;
5098
+ if (target.value.hasParamsInPipeArgs) continue;
5099
+ const { effectModuleName, nameIdentifier, node: targetNode, pipeArguments: pipeArguments2, traceName } = target.value;
5100
+ const innerFunction = target.value.generatorFunction ?? targetNode;
4850
5101
  const fixes = [];
4851
5102
  fixes.push({
4852
5103
  fixName: "effectFnOpportunity_toEffectFn",
4853
5104
  description: traceName ? `Convert to Effect.fn("${traceName}")` : "Convert to Effect.fn",
4854
5105
  apply: gen(function* () {
4855
5106
  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
- );
5107
+ const newNode = createEffectFnNode(targetNode, innerFunction, effectModuleName, traceName, pipeArguments2);
4879
5108
  changeTracker.replaceNode(sourceFile, targetNode, newNode);
4880
5109
  })
4881
5110
  });
5111
+ if (target.value.generatorFunction) {
5112
+ fixes.push({
5113
+ fixName: "effectFnOpportunity_toEffectFnUntraced",
5114
+ description: "Convert to Effect.fnUntraced",
5115
+ apply: gen(function* () {
5116
+ const changeTracker = yield* service(ChangeTracker);
5117
+ const newNode = createEffectFnUntracedNode(targetNode, innerFunction, effectModuleName, pipeArguments2);
5118
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
5119
+ })
5120
+ });
5121
+ }
5122
+ const pipeArgsSuffix = pipeArguments2.length > 0 ? ` Effect.fn also accepts the piped transformations as additional arguments.` : ``;
4882
5123
  report({
4883
5124
  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.`,
5125
+ messageText: target.value.generatorFunction ? `This function could benefit from Effect.fn's automatic tracing and concise syntax, or Effect.fnUntraced to get just a more concise syntax.${pipeArgsSuffix}` : `This function could benefit from Effect.fn's automatic tracing and concise syntax.${pipeArgsSuffix}`,
4885
5126
  fixes
4886
5127
  });
4887
5128
  }
4888
5129
  })
4889
5130
  });
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
5131
 
4918
5132
  // src/diagnostics/effectGenUsesAdapter.ts
4919
5133
  var effectGenUsesAdapter = createDiagnostic({
@@ -5676,6 +5890,52 @@ var missedPipeableOpportunity = createDiagnostic({
5676
5890
  const typeChecker = yield* service(TypeCheckerApi);
5677
5891
  const typeParser = yield* service(TypeParser);
5678
5892
  const options = yield* service(LanguageServicePluginOptions);
5893
+ const isSafelyPipeableCallee = (callee) => {
5894
+ if (ts.isCallExpression(callee)) {
5895
+ return true;
5896
+ }
5897
+ if (ts.isArrowFunction(callee)) {
5898
+ return true;
5899
+ }
5900
+ if (ts.isFunctionExpression(callee)) {
5901
+ return true;
5902
+ }
5903
+ if (ts.isParenthesizedExpression(callee)) {
5904
+ return isSafelyPipeableCallee(callee.expression);
5905
+ }
5906
+ if (ts.isIdentifier(callee)) {
5907
+ const symbol3 = typeChecker.getSymbolAtLocation(callee);
5908
+ if (!symbol3) return false;
5909
+ if (symbol3.flags & (ts.SymbolFlags.Module | ts.SymbolFlags.Namespace | ts.SymbolFlags.ValueModule)) {
5910
+ return true;
5911
+ }
5912
+ const declarations = symbol3.declarations;
5913
+ if (declarations && declarations.length > 0) {
5914
+ const decl = declarations[0];
5915
+ if (ts.isFunctionDeclaration(decl) || ts.isVariableDeclaration(decl) || ts.isImportSpecifier(decl) || ts.isImportClause(decl) || ts.isNamespaceImport(decl)) {
5916
+ return true;
5917
+ }
5918
+ }
5919
+ return false;
5920
+ }
5921
+ if (ts.isPropertyAccessExpression(callee)) {
5922
+ const subject = callee.expression;
5923
+ const symbol3 = typeChecker.getSymbolAtLocation(subject);
5924
+ if (!symbol3) return false;
5925
+ if (symbol3.flags & (ts.SymbolFlags.Module | ts.SymbolFlags.Namespace | ts.SymbolFlags.ValueModule)) {
5926
+ return true;
5927
+ }
5928
+ const declarations = symbol3.declarations;
5929
+ if (declarations && declarations.length > 0) {
5930
+ const decl = declarations[0];
5931
+ if (ts.isNamespaceImport(decl) || ts.isSourceFile(decl) || ts.isModuleDeclaration(decl)) {
5932
+ return true;
5933
+ }
5934
+ }
5935
+ return false;
5936
+ }
5937
+ return false;
5938
+ };
5679
5939
  const flows = yield* typeParser.pipingFlows(false)(sourceFile);
5680
5940
  for (const flow2 of flows) {
5681
5941
  if (flow2.transformations.length < options.pipeableMinArgCount) {
@@ -5689,76 +5949,92 @@ var missedPipeableOpportunity = createDiagnostic({
5689
5949
  if (callSigs.length > 0) {
5690
5950
  continue;
5691
5951
  }
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++) {
5952
+ const isPipeableAtIndex = function* (index) {
5953
+ if (index === 0) {
5954
+ const subjectType = flow2.subject.outType;
5955
+ if (!subjectType) return false;
5956
+ const result = yield* pipe(
5957
+ typeParser.pipeableType(subjectType, flow2.subject.node),
5958
+ option
5959
+ );
5960
+ return result._tag === "Some";
5961
+ } else {
5962
+ const t = flow2.transformations[index - 1];
5963
+ if (!t.outType) return false;
5964
+ const result = yield* pipe(
5965
+ typeParser.pipeableType(t.outType, flow2.node),
5966
+ option
5967
+ );
5968
+ return result._tag === "Some";
5969
+ }
5970
+ };
5971
+ let searchStartIndex = 0;
5972
+ while (searchStartIndex <= flow2.transformations.length) {
5973
+ let firstPipeableIndex = -1;
5974
+ for (let i = searchStartIndex; i <= flow2.transformations.length; i++) {
5975
+ if (yield* isPipeableAtIndex(i)) {
5976
+ firstPipeableIndex = i;
5977
+ break;
5978
+ }
5979
+ }
5980
+ if (firstPipeableIndex === -1) {
5981
+ break;
5982
+ }
5983
+ const pipeableTransformations = [];
5984
+ for (let i = firstPipeableIndex; i < flow2.transformations.length; i++) {
5705
5985
  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;
5714
- }
5986
+ if (!isSafelyPipeableCallee(t.callee)) {
5987
+ break;
5715
5988
  }
5989
+ pipeableTransformations.push(t);
5716
5990
  }
5717
- }
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,
5991
+ const callKindCount = pipeableTransformations.filter((t) => t.kind === "call").length;
5992
+ if (callKindCount >= options.pipeableMinArgCount) {
5993
+ const pipeableEndIndex = firstPipeableIndex + pipeableTransformations.length;
5994
+ const pipeableSubjectNode = firstPipeableIndex === 0 ? flow2.subject.node : typeParser.reconstructPipingFlow({
5995
+ subject: flow2.subject,
5996
+ transformations: flow2.transformations.slice(0, firstPipeableIndex)
5997
+ });
5998
+ const afterTransformations = flow2.transformations.slice(pipeableEndIndex);
5999
+ report({
6000
+ location: flow2.node,
6001
+ messageText: `Nested function calls can be converted to pipeable style for better readability.`,
6002
+ fixes: [{
6003
+ fixName: "missedPipeableOpportunity_fix",
6004
+ description: "Convert to pipe style",
6005
+ apply: gen(function* () {
6006
+ const changeTracker = yield* service(ChangeTracker);
6007
+ const pipeArgs = pipeableTransformations.map((t) => {
6008
+ if (t.args) {
6009
+ return ts.factory.createCallExpression(
6010
+ t.callee,
6011
+ void 0,
6012
+ t.args
6013
+ );
6014
+ } else {
6015
+ return t.callee;
6016
+ }
6017
+ });
6018
+ const pipeNode = ts.factory.createCallExpression(
6019
+ ts.factory.createPropertyAccessExpression(
6020
+ pipeableSubjectNode,
6021
+ "pipe"
6022
+ ),
5743
6023
  void 0,
5744
- t.args
6024
+ pipeArgs
5745
6025
  );
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
- });
6026
+ const newNode = afterTransformations.length > 0 ? typeParser.reconstructPipingFlow({
6027
+ subject: { node: pipeNode, outType: void 0 },
6028
+ transformations: afterTransformations
6029
+ }) : pipeNode;
6030
+ changeTracker.replaceNode(sourceFile, flow2.node, newNode);
6031
+ })
6032
+ }]
6033
+ });
6034
+ break;
6035
+ }
6036
+ searchStartIndex = firstPipeableIndex + pipeableTransformations.length + 1;
6037
+ }
5762
6038
  }
5763
6039
  })
5764
6040
  });
@@ -7637,6 +7913,90 @@ var overriddenSchemaConstructor = createDiagnostic({
7637
7913
  })
7638
7914
  });
7639
7915
 
7916
+ // src/diagnostics/preferSchemaOverJson.ts
7917
+ var preferSchemaOverJson = createDiagnostic({
7918
+ name: "preferSchemaOverJson",
7919
+ code: 44,
7920
+ description: "Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify which may throw",
7921
+ severity: "suggestion",
7922
+ apply: fn("preferSchemaOverJson.apply")(function* (sourceFile, report) {
7923
+ const ts = yield* service(TypeScriptApi);
7924
+ const typeParser = yield* service(TypeParser);
7925
+ const parseJsonMethod = (node) => gen(function* () {
7926
+ if (!ts.isCallExpression(node)) return yield* fail("node is not a call expression");
7927
+ const expression = node.expression;
7928
+ if (!ts.isPropertyAccessExpression(expression)) return yield* fail("expression is not a property access");
7929
+ const objectExpr = expression.expression;
7930
+ const methodName = ts.idText(expression.name);
7931
+ if (!ts.isIdentifier(objectExpr) || ts.idText(objectExpr) !== "JSON") {
7932
+ return yield* fail("object is not JSON");
7933
+ }
7934
+ if (methodName !== "parse" && methodName !== "stringify") {
7935
+ return yield* fail("method is not parse or stringify");
7936
+ }
7937
+ return { node, methodName };
7938
+ });
7939
+ const effectTrySimple = (node) => gen(function* () {
7940
+ if (!ts.isCallExpression(node)) return yield* fail("node is not a call expression");
7941
+ yield* typeParser.isNodeReferenceToEffectModuleApi("try")(node.expression);
7942
+ if (node.arguments.length === 0) return yield* fail("Effect.try has no arguments");
7943
+ const lazyFn = yield* typeParser.lazyExpression(node.arguments[0]);
7944
+ const jsonMethod = yield* parseJsonMethod(lazyFn.expression);
7945
+ return { node: jsonMethod.node, methodName: jsonMethod.methodName };
7946
+ });
7947
+ const effectTryObject = (node) => gen(function* () {
7948
+ if (!ts.isCallExpression(node)) return yield* fail("node is not a call expression");
7949
+ yield* typeParser.isNodeReferenceToEffectModuleApi("try")(node.expression);
7950
+ if (node.arguments.length === 0) return yield* fail("Effect.try has no arguments");
7951
+ const arg = node.arguments[0];
7952
+ if (!ts.isObjectLiteralExpression(arg)) return yield* fail("argument is not an object literal");
7953
+ const tryProp = arg.properties.find(
7954
+ (p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && ts.idText(p.name) === "try"
7955
+ );
7956
+ if (!tryProp) return yield* fail("object has no 'try' property");
7957
+ const lazyFn = yield* typeParser.lazyExpression(tryProp.initializer);
7958
+ const jsonMethod = yield* parseJsonMethod(lazyFn.expression);
7959
+ return { node: jsonMethod.node, methodName: jsonMethod.methodName };
7960
+ });
7961
+ const jsonMethodInEffectGen = (node) => gen(function* () {
7962
+ const jsonMethod = yield* parseJsonMethod(node);
7963
+ const { effectGen, scopeNode } = yield* typeParser.findEnclosingScopes(node);
7964
+ if (!effectGen || effectGen.body.statements.length === 0) {
7965
+ return yield* fail("not inside an Effect generator");
7966
+ }
7967
+ if (scopeNode && scopeNode !== effectGen.generatorFunction) {
7968
+ return yield* fail("inside a nested function scope");
7969
+ }
7970
+ return { node: jsonMethod.node, methodName: jsonMethod.methodName };
7971
+ });
7972
+ const nodeToVisit = [];
7973
+ const appendNodeToVisit = (node) => {
7974
+ nodeToVisit.push(node);
7975
+ return void 0;
7976
+ };
7977
+ ts.forEachChild(sourceFile, appendNodeToVisit);
7978
+ while (nodeToVisit.length > 0) {
7979
+ const node = nodeToVisit.shift();
7980
+ ts.forEachChild(node, appendNodeToVisit);
7981
+ const match2 = yield* pipe(
7982
+ firstSuccessOf([
7983
+ effectTrySimple(node),
7984
+ effectTryObject(node),
7985
+ jsonMethodInEffectGen(node)
7986
+ ]),
7987
+ option
7988
+ );
7989
+ if (isSome2(match2)) {
7990
+ report({
7991
+ location: match2.value.node,
7992
+ messageText: "Consider using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify",
7993
+ fixes: []
7994
+ });
7995
+ }
7996
+ }
7997
+ })
7998
+ });
7999
+
7640
8000
  // src/diagnostics/redundantSchemaTagIdentifier.ts
7641
8001
  var redundantSchemaTagIdentifier = createDiagnostic({
7642
8002
  name: "redundantSchemaTagIdentifier",
@@ -7789,108 +8149,91 @@ var runEffectInsideEffect = createDiagnostic({
7789
8149
  option
7790
8150
  );
7791
8151
  if (isNone2(isEffectRunCall)) continue;
7792
- let currentParent = node.parent;
7793
- let nodeIntroduceScope = void 0;
7794
- while (currentParent) {
7795
- const possiblyEffectGen = currentParent;
7796
- if (!nodeIntroduceScope) {
7797
- if (ts.isFunctionExpression(possiblyEffectGen) || ts.isFunctionDeclaration(possiblyEffectGen) || ts.isMethodDeclaration(possiblyEffectGen) || ts.isArrowFunction(possiblyEffectGen)) {
7798
- nodeIntroduceScope = possiblyEffectGen;
7799
- continue;
7800
- }
7801
- }
7802
- const isInEffectGen = yield* pipe(
7803
- typeParser.effectGen(possiblyEffectGen),
7804
- orElse2(() => typeParser.effectFnUntracedGen(possiblyEffectGen)),
7805
- orElse2(() => typeParser.effectFnGen(possiblyEffectGen)),
7806
- option
8152
+ const { effectGen, scopeNode } = yield* typeParser.findEnclosingScopes(node);
8153
+ if (effectGen && effectGen.body.statements.length > 0) {
8154
+ const nodeText = sourceFile.text.substring(
8155
+ ts.getTokenPosOfNode(node.expression, sourceFile),
8156
+ node.expression.end
7807
8157
  );
7808
- if (isSome2(isInEffectGen) && isInEffectGen.value.body.statements.length > 0) {
7809
- const nodeText = sourceFile.text.substring(
7810
- ts.getTokenPosOfNode(node.expression, sourceFile),
7811
- node.expression.end
7812
- );
7813
- if (nodeIntroduceScope && nodeIntroduceScope !== isInEffectGen.value.generatorFunction) {
7814
- const fixAddRuntime = gen(function* () {
7815
- const changeTracker = yield* service(ChangeTracker);
7816
- const runtimeModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Runtime") || "Runtime";
7817
- const effectModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect") || "Effect";
7818
- let runtimeIdentifier = void 0;
7819
- for (const statement of isInEffectGen.value.generatorFunction.body.statements) {
7820
- if (ts.isVariableStatement(statement) && statement.declarationList.declarations.length === 1) {
7821
- const declaration = statement.declarationList.declarations[0];
7822
- if (declaration.initializer && ts.isYieldExpression(declaration.initializer) && declaration.initializer.asteriskToken && declaration.initializer.expression) {
7823
- const yieldedExpression = declaration.initializer.expression;
7824
- if (ts.isCallExpression(yieldedExpression)) {
7825
- const maybeEffectRuntime = yield* pipe(
7826
- typeParser.isNodeReferenceToEffectModuleApi("runtime")(yieldedExpression.expression),
7827
- option
7828
- );
7829
- if (isSome2(maybeEffectRuntime) && ts.isIdentifier(declaration.name)) {
7830
- runtimeIdentifier = ts.idText(declaration.name);
7831
- }
8158
+ if (scopeNode && scopeNode !== effectGen.generatorFunction) {
8159
+ const fixAddRuntime = gen(function* () {
8160
+ const changeTracker = yield* service(ChangeTracker);
8161
+ const runtimeModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Runtime") || "Runtime";
8162
+ const effectModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect") || "Effect";
8163
+ let runtimeIdentifier = void 0;
8164
+ for (const statement of effectGen.generatorFunction.body.statements) {
8165
+ if (ts.isVariableStatement(statement) && statement.declarationList.declarations.length === 1) {
8166
+ const declaration = statement.declarationList.declarations[0];
8167
+ if (declaration.initializer && ts.isYieldExpression(declaration.initializer) && declaration.initializer.asteriskToken && declaration.initializer.expression) {
8168
+ const yieldedExpression = declaration.initializer.expression;
8169
+ if (ts.isCallExpression(yieldedExpression)) {
8170
+ const maybeEffectRuntime = yield* pipe(
8171
+ typeParser.isNodeReferenceToEffectModuleApi("runtime")(yieldedExpression.expression),
8172
+ option
8173
+ );
8174
+ if (isSome2(maybeEffectRuntime) && ts.isIdentifier(declaration.name)) {
8175
+ runtimeIdentifier = ts.idText(declaration.name);
7832
8176
  }
7833
8177
  }
7834
8178
  }
7835
8179
  }
7836
- if (!runtimeIdentifier) {
7837
- changeTracker.insertNodeAt(
7838
- sourceFile,
7839
- isInEffectGen.value.body.statements[0].pos,
7840
- ts.factory.createVariableStatement(
8180
+ }
8181
+ if (!runtimeIdentifier) {
8182
+ changeTracker.insertNodeAt(
8183
+ sourceFile,
8184
+ effectGen.body.statements[0].pos,
8185
+ ts.factory.createVariableStatement(
8186
+ void 0,
8187
+ ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(
8188
+ "effectRuntime",
7841
8189
  void 0,
7842
- ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(
7843
- "effectRuntime",
7844
- void 0,
7845
- void 0,
7846
- ts.factory.createYieldExpression(
7847
- ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
7848
- ts.factory.createCallExpression(
7849
- ts.factory.createPropertyAccessExpression(
7850
- ts.factory.createIdentifier(effectModuleIdentifier),
7851
- "runtime"
7852
- ),
7853
- [ts.factory.createTypeReferenceNode("never")],
7854
- []
7855
- )
8190
+ void 0,
8191
+ ts.factory.createYieldExpression(
8192
+ ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
8193
+ ts.factory.createCallExpression(
8194
+ ts.factory.createPropertyAccessExpression(
8195
+ ts.factory.createIdentifier(effectModuleIdentifier),
8196
+ "runtime"
8197
+ ),
8198
+ [ts.factory.createTypeReferenceNode("never")],
8199
+ []
7856
8200
  )
7857
- )], ts.NodeFlags.Const)
7858
- ),
7859
- {
7860
- prefix: "\n",
7861
- suffix: "\n"
7862
- }
7863
- );
7864
- }
7865
- changeTracker.deleteRange(sourceFile, {
7866
- pos: ts.getTokenPosOfNode(node.expression, sourceFile),
7867
- end: node.arguments[0].pos
7868
- });
7869
- changeTracker.insertText(
7870
- sourceFile,
7871
- node.arguments[0].pos,
7872
- `${runtimeModuleIdentifier}.${isEffectRunCall.value.methodName}(${runtimeIdentifier || "effectRuntime"}, `
8201
+ )
8202
+ )], ts.NodeFlags.Const)
8203
+ ),
8204
+ {
8205
+ prefix: "\n",
8206
+ suffix: "\n"
8207
+ }
7873
8208
  );
8209
+ }
8210
+ changeTracker.deleteRange(sourceFile, {
8211
+ pos: ts.getTokenPosOfNode(node.expression, sourceFile),
8212
+ end: node.arguments[0].pos
7874
8213
  });
7875
- report({
7876
- location: node.expression,
7877
- messageText: `Using ${nodeText} inside an Effect is not recommended. The same runtime should generally be used instead to run child effects.
8214
+ changeTracker.insertText(
8215
+ sourceFile,
8216
+ node.arguments[0].pos,
8217
+ `${runtimeModuleIdentifier}.${isEffectRunCall.value.methodName}(${runtimeIdentifier || "effectRuntime"}, `
8218
+ );
8219
+ });
8220
+ report({
8221
+ location: node.expression,
8222
+ messageText: `Using ${nodeText} inside an Effect is not recommended. The same runtime should generally be used instead to run child effects.
7878
8223
  Consider extracting the Runtime by using for example Effect.runtime and then use Runtime.${isEffectRunCall.value.methodName} with the extracted runtime instead.`,
7879
- fixes: [{
7880
- fixName: "runEffectInsideEffect_fix",
7881
- description: "Use a runtime to run the Effect",
7882
- apply: fixAddRuntime
7883
- }]
7884
- });
7885
- } else {
7886
- report({
7887
- location: node.expression,
7888
- messageText: `Using ${nodeText} inside an Effect is not recommended. Effects inside generators can usually just be yielded.`,
7889
- fixes: []
7890
- });
7891
- }
8224
+ fixes: [{
8225
+ fixName: "runEffectInsideEffect_fix",
8226
+ description: "Use a runtime to run the Effect",
8227
+ apply: fixAddRuntime
8228
+ }]
8229
+ });
8230
+ } else {
8231
+ report({
8232
+ location: node.expression,
8233
+ messageText: `Using ${nodeText} inside an Effect is not recommended. Effects inside generators can usually just be yielded.`,
8234
+ fixes: []
8235
+ });
7892
8236
  }
7893
- currentParent = currentParent.parent;
7894
8237
  }
7895
8238
  }
7896
8239
  })
@@ -7976,6 +8319,59 @@ var schemaStructWithTag = createDiagnostic({
7976
8319
  })
7977
8320
  });
7978
8321
 
8322
+ // src/diagnostics/schemaSyncInEffect.ts
8323
+ var syncToEffectMethod = {
8324
+ decodeSync: "decode",
8325
+ decodeUnknownSync: "decodeUnknown",
8326
+ encodeSync: "encode",
8327
+ encodeUnknownSync: "encodeUnknown"
8328
+ };
8329
+ var schemaSyncInEffect = createDiagnostic({
8330
+ name: "schemaSyncInEffect",
8331
+ code: 43,
8332
+ description: "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
8333
+ severity: "suggestion",
8334
+ apply: fn("schemaSyncInEffect.apply")(function* (sourceFile, report) {
8335
+ const ts = yield* service(TypeScriptApi);
8336
+ const typeParser = yield* service(TypeParser);
8337
+ const parseSchemaSyncMethod = (node, methodName) => pipe(
8338
+ typeParser.isNodeReferenceToEffectParseResultModuleApi(methodName)(node),
8339
+ map4(() => ({ node, methodName }))
8340
+ );
8341
+ const nodeToVisit = [];
8342
+ const appendNodeToVisit = (node) => {
8343
+ nodeToVisit.push(node);
8344
+ return void 0;
8345
+ };
8346
+ ts.forEachChild(sourceFile, appendNodeToVisit);
8347
+ while (nodeToVisit.length > 0) {
8348
+ const node = nodeToVisit.shift();
8349
+ ts.forEachChild(node, appendNodeToVisit);
8350
+ if (!ts.isCallExpression(node)) continue;
8351
+ const isSchemaSyncCall = yield* pipe(
8352
+ firstSuccessOf(
8353
+ Object.keys(syncToEffectMethod).map((methodName) => parseSchemaSyncMethod(node.expression, methodName))
8354
+ ),
8355
+ option
8356
+ );
8357
+ if (isNone2(isSchemaSyncCall)) continue;
8358
+ const { effectGen, scopeNode } = yield* typeParser.findEnclosingScopes(node);
8359
+ if (!effectGen || effectGen.body.statements.length === 0) continue;
8360
+ if (scopeNode && scopeNode !== effectGen.generatorFunction) continue;
8361
+ const nodeText = sourceFile.text.substring(
8362
+ ts.getTokenPosOfNode(node.expression, sourceFile),
8363
+ node.expression.end
8364
+ );
8365
+ const effectMethodName = syncToEffectMethod[isSchemaSyncCall.value.methodName];
8366
+ report({
8367
+ location: node.expression,
8368
+ messageText: `Using ${nodeText} inside an Effect generator is not recommended. Use Schema.${effectMethodName} instead to get properly typed ParseError in the error channel.`,
8369
+ fixes: []
8370
+ });
8371
+ }
8372
+ })
8373
+ });
8374
+
7979
8375
  // src/diagnostics/schemaUnionOfLiterals.ts
7980
8376
  var schemaUnionOfLiterals = createDiagnostic({
7981
8377
  name: "schemaUnionOfLiterals",
@@ -8690,7 +9086,9 @@ var diagnostics = [
8690
9086
  layerMergeAllWithDependencies,
8691
9087
  effectMapVoid,
8692
9088
  effectFnOpportunity,
8693
- redundantSchemaTagIdentifier
9089
+ redundantSchemaTagIdentifier,
9090
+ schemaSyncInEffect,
9091
+ preferSchemaOverJson
8694
9092
  ];
8695
9093
 
8696
9094
  // src/effect-lsp-patch-utils.ts