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