@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/index.js CHANGED
@@ -5437,6 +5437,97 @@ function make7(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
5437
5437
  "TypeParser.effectFnGen",
5438
5438
  (node) => node
5439
5439
  );
5440
+ const findEnclosingScopes = fn("TypeParser.findEnclosingScopes")(function* (startNode) {
5441
+ let currentParent = startNode.parent;
5442
+ let scopeNode = void 0;
5443
+ let effectGenResult = void 0;
5444
+ while (currentParent) {
5445
+ const nodeToCheck = currentParent;
5446
+ if (!scopeNode) {
5447
+ if (ts.isFunctionExpression(nodeToCheck) || ts.isFunctionDeclaration(nodeToCheck) || ts.isMethodDeclaration(nodeToCheck) || ts.isArrowFunction(nodeToCheck) || ts.isGetAccessorDeclaration(nodeToCheck) || ts.isSetAccessorDeclaration(nodeToCheck)) {
5448
+ scopeNode = nodeToCheck;
5449
+ }
5450
+ }
5451
+ if (!effectGenResult) {
5452
+ const isEffectGen = yield* pipe(
5453
+ effectGen(nodeToCheck),
5454
+ map8((result) => ({
5455
+ node: result.node,
5456
+ effectModule: result.effectModule,
5457
+ generatorFunction: result.generatorFunction,
5458
+ body: result.body
5459
+ })),
5460
+ orElse2(
5461
+ () => pipe(
5462
+ effectFnUntracedGen(nodeToCheck),
5463
+ map8((result) => ({
5464
+ node: result.node,
5465
+ effectModule: result.effectModule,
5466
+ generatorFunction: result.generatorFunction,
5467
+ body: result.body,
5468
+ pipeArguments: result.pipeArguments
5469
+ }))
5470
+ )
5471
+ ),
5472
+ orElse2(
5473
+ () => pipe(
5474
+ effectFnGen(nodeToCheck),
5475
+ map8((result) => ({
5476
+ node: result.node,
5477
+ effectModule: result.effectModule,
5478
+ generatorFunction: result.generatorFunction,
5479
+ body: result.body,
5480
+ pipeArguments: result.pipeArguments
5481
+ }))
5482
+ )
5483
+ ),
5484
+ option
5485
+ );
5486
+ if (isSome2(isEffectGen)) {
5487
+ effectGenResult = isEffectGen.value;
5488
+ }
5489
+ }
5490
+ if (scopeNode && effectGenResult) {
5491
+ break;
5492
+ }
5493
+ currentParent = nodeToCheck.parent;
5494
+ }
5495
+ return { scopeNode, effectGen: effectGenResult };
5496
+ });
5497
+ const effectFn = cachedBy(
5498
+ function(node) {
5499
+ if (!ts.isCallExpression(node)) {
5500
+ return typeParserIssue("Node is not a call expression", void 0, node);
5501
+ }
5502
+ if (node.arguments.length === 0) {
5503
+ return typeParserIssue("Node has no arguments", void 0, node);
5504
+ }
5505
+ const regularFunction = node.arguments[0];
5506
+ if (!ts.isFunctionExpression(regularFunction) && !ts.isArrowFunction(regularFunction)) {
5507
+ return typeParserIssue("Node is not a function expression or arrow function", void 0, node);
5508
+ }
5509
+ if (ts.isFunctionExpression(regularFunction) && regularFunction.asteriskToken !== void 0) {
5510
+ return typeParserIssue("Node is a generator function, not a regular function", void 0, node);
5511
+ }
5512
+ const expressionToTest = ts.isCallExpression(node.expression) ? node.expression.expression : node.expression;
5513
+ if (!ts.isPropertyAccessExpression(expressionToTest)) {
5514
+ return typeParserIssue("Node is not a property access expression", void 0, node);
5515
+ }
5516
+ const propertyAccess = expressionToTest;
5517
+ const pipeArguments2 = node.arguments.slice(1);
5518
+ return pipe(
5519
+ isNodeReferenceToEffectModuleApi("fn")(propertyAccess),
5520
+ map8(() => ({
5521
+ node,
5522
+ effectModule: propertyAccess.expression,
5523
+ regularFunction,
5524
+ pipeArguments: pipeArguments2
5525
+ }))
5526
+ );
5527
+ },
5528
+ "TypeParser.effectFn",
5529
+ (node) => node
5530
+ );
5440
5531
  const unnecessaryEffectGen2 = cachedBy(
5441
5532
  fn("TypeParser.unnecessaryEffectGen")(function* (node) {
5442
5533
  const { body } = yield* effectGen(node);
@@ -5548,6 +5639,28 @@ function make7(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
5548
5639
  `TypeParser.isNodeReferenceToEffectSchemaModuleApi(${memberName})`,
5549
5640
  (node) => node
5550
5641
  );
5642
+ const isEffectParseResultSourceFile = cachedBy(
5643
+ fn("TypeParser.isEffectParseResultSourceFile")(function* (sourceFile) {
5644
+ const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
5645
+ if (!moduleSymbol) return yield* typeParserIssue("Node has no symbol", void 0, sourceFile);
5646
+ const parseIssueSymbol = typeChecker.tryGetMemberInModuleExports("ParseIssue", moduleSymbol);
5647
+ if (!parseIssueSymbol) return yield* typeParserIssue("ParseIssue type not found", void 0, sourceFile);
5648
+ const decodeSyncSymbol = typeChecker.tryGetMemberInModuleExports("decodeSync", moduleSymbol);
5649
+ if (!decodeSyncSymbol) return yield* typeParserIssue("decodeSync not found", void 0, sourceFile);
5650
+ const encodeSyncSymbol = typeChecker.tryGetMemberInModuleExports("encodeSync", moduleSymbol);
5651
+ if (!encodeSyncSymbol) return yield* typeParserIssue("encodeSync not found", void 0, sourceFile);
5652
+ return sourceFile;
5653
+ }),
5654
+ "TypeParser.isEffectParseResultSourceFile",
5655
+ (sourceFile) => sourceFile
5656
+ );
5657
+ const isNodeReferenceToEffectParseResultModuleApi = (memberName) => cachedBy(
5658
+ fn("TypeParser.isNodeReferenceToEffectParseResultModuleApi")(function* (node) {
5659
+ return yield* isNodeReferenceToExportOfPackageModule(node, "effect", isEffectParseResultSourceFile, memberName);
5660
+ }),
5661
+ `TypeParser.isNodeReferenceToEffectParseResultModuleApi(${memberName})`,
5662
+ (node) => node
5663
+ );
5551
5664
  const contextTagVarianceStruct = (type, atLocation) => map8(
5552
5665
  all(
5553
5666
  varianceStructInvariantType(type, atLocation, "_Identifier"),
@@ -6328,11 +6441,65 @@ function make7(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
6328
6441
  if (includeEffectFn) {
6329
6442
  const effectFnGenParsed = yield* pipe(effectFnGen(node), option);
6330
6443
  const effectFnUntracedGenParsed = isNone2(effectFnGenParsed) ? yield* pipe(effectFnUntracedGen(node), option) : none2();
6331
- const isEffectFn = isSome2(effectFnGenParsed);
6332
- const effectFnParsed = isEffectFn ? effectFnGenParsed : effectFnUntracedGenParsed;
6333
- const transformationKind = isEffectFn ? "effectFn" : "effectFnUntraced";
6334
- if (isSome2(effectFnParsed) && effectFnParsed.value.pipeArguments.length > 0) {
6335
- const fnResult = effectFnParsed.value;
6444
+ const effectFnNonGenParsed = isNone2(effectFnGenParsed) && isNone2(effectFnUntracedGenParsed) ? yield* pipe(effectFn(node), option) : none2();
6445
+ const isEffectFnGen = isSome2(effectFnGenParsed);
6446
+ const isEffectFnUntracedGen = isSome2(effectFnUntracedGenParsed);
6447
+ const isEffectFnNonGen = isSome2(effectFnNonGenParsed);
6448
+ const transformationKind = isEffectFnUntracedGen ? "effectFnUntraced" : "effectFn";
6449
+ if (isEffectFnGen || isEffectFnUntracedGen) {
6450
+ const effectFnParsed = isEffectFnGen ? effectFnGenParsed : effectFnUntracedGenParsed;
6451
+ if (isSome2(effectFnParsed) && effectFnParsed.value.pipeArguments.length > 0) {
6452
+ const fnResult = effectFnParsed.value;
6453
+ const pipeArgs = fnResult.pipeArguments;
6454
+ const transformations = [];
6455
+ let subjectType;
6456
+ for (let i = 0; i < pipeArgs.length; i++) {
6457
+ const arg = pipeArgs[i];
6458
+ const contextualType = typeChecker.getContextualType(arg);
6459
+ const callSigs = contextualType ? typeChecker.getSignaturesOfType(contextualType, ts.SignatureKind.Call) : [];
6460
+ const outType = callSigs.length > 0 ? typeChecker.getReturnTypeOfSignature(callSigs[0]) : void 0;
6461
+ if (i === 0 && callSigs.length > 0) {
6462
+ const params = callSigs[0].parameters;
6463
+ if (params.length > 0) {
6464
+ subjectType = typeChecker.getTypeOfSymbol(params[0]);
6465
+ }
6466
+ }
6467
+ if (ts.isCallExpression(arg)) {
6468
+ transformations.push({
6469
+ callee: arg.expression,
6470
+ args: Array.from(arg.arguments),
6471
+ outType,
6472
+ kind: transformationKind
6473
+ });
6474
+ } else {
6475
+ transformations.push({
6476
+ callee: arg,
6477
+ args: void 0,
6478
+ outType,
6479
+ kind: transformationKind
6480
+ });
6481
+ }
6482
+ }
6483
+ const newFlow = {
6484
+ node,
6485
+ subject: {
6486
+ node,
6487
+ outType: subjectType
6488
+ },
6489
+ transformations
6490
+ };
6491
+ result.push(newFlow);
6492
+ workQueue.push([fnResult.body, void 0]);
6493
+ for (const arg of pipeArgs) {
6494
+ ts.forEachChild(arg, (c) => {
6495
+ workQueue.push([c, void 0]);
6496
+ });
6497
+ }
6498
+ continue;
6499
+ }
6500
+ }
6501
+ if (isEffectFnNonGen && isSome2(effectFnNonGenParsed) && effectFnNonGenParsed.value.pipeArguments.length > 0) {
6502
+ const fnResult = effectFnNonGenParsed.value;
6336
6503
  const pipeArgs = fnResult.pipeArguments;
6337
6504
  const transformations = [];
6338
6505
  let subjectType;
@@ -6352,14 +6519,14 @@ function make7(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
6352
6519
  callee: arg.expression,
6353
6520
  args: Array.from(arg.arguments),
6354
6521
  outType,
6355
- kind: transformationKind
6522
+ kind: "effectFn"
6356
6523
  });
6357
6524
  } else {
6358
6525
  transformations.push({
6359
6526
  callee: arg,
6360
6527
  args: void 0,
6361
6528
  outType,
6362
- kind: transformationKind
6529
+ kind: "effectFn"
6363
6530
  });
6364
6531
  }
6365
6532
  }
@@ -6372,7 +6539,16 @@ function make7(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
6372
6539
  transformations
6373
6540
  };
6374
6541
  result.push(newFlow);
6375
- workQueue.push([fnResult.body, void 0]);
6542
+ const regularFn = fnResult.regularFunction;
6543
+ if (ts.isArrowFunction(regularFn)) {
6544
+ if (ts.isBlock(regularFn.body)) {
6545
+ workQueue.push([regularFn.body, void 0]);
6546
+ } else {
6547
+ workQueue.push([regularFn.body, void 0]);
6548
+ }
6549
+ } else if (regularFn.body) {
6550
+ workQueue.push([regularFn.body, void 0]);
6551
+ }
6376
6552
  for (const arg of pipeArgs) {
6377
6553
  ts.forEachChild(arg, (c) => {
6378
6554
  workQueue.push([c, void 0]);
@@ -6435,6 +6611,7 @@ function make7(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
6435
6611
  return {
6436
6612
  isNodeReferenceToEffectModuleApi,
6437
6613
  isNodeReferenceToEffectSchemaModuleApi,
6614
+ isNodeReferenceToEffectParseResultModuleApi,
6438
6615
  isNodeReferenceToEffectDataModuleApi,
6439
6616
  isNodeReferenceToEffectContextModuleApi,
6440
6617
  isNodeReferenceToEffectSqlModelModuleApi,
@@ -6448,6 +6625,8 @@ function make7(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
6448
6625
  effectGen,
6449
6626
  effectFnUntracedGen,
6450
6627
  effectFnGen,
6628
+ findEnclosingScopes,
6629
+ effectFn,
6451
6630
  extendsCauseYieldableError,
6452
6631
  unnecessaryEffectGen: unnecessaryEffectGen2,
6453
6632
  effectSchemaType,
@@ -8147,106 +8326,195 @@ ${versions.map((version) => `- found ${version} at ${resolvedPackages[packageNam
8147
8326
  });
8148
8327
 
8149
8328
  // src/diagnostics/effectFnOpportunity.ts
8150
- var parseEffectFnOpportunityTarget = (node, sourceFile) => gen(function* () {
8151
- const ts = yield* service(TypeScriptApi);
8152
- const typeChecker = yield* service(TypeCheckerApi);
8153
- const typeParser = yield* service(TypeParser);
8154
- const tsUtils = yield* service(TypeScriptUtils);
8155
- if (!ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isFunctionDeclaration(node)) {
8156
- return yield* TypeParserIssue.issue;
8157
- }
8158
- if ((ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) && node.asteriskToken) {
8159
- return yield* TypeParserIssue.issue;
8160
- }
8161
- if (ts.isFunctionExpression(node) && node.name) {
8162
- return yield* TypeParserIssue.issue;
8163
- }
8164
- let bodyExpression;
8165
- if (ts.isArrowFunction(node)) {
8166
- if (ts.isBlock(node.body)) {
8167
- const returnStatement = findSingleReturnStatement(ts, node.body);
8168
- if (returnStatement?.expression) {
8169
- bodyExpression = returnStatement.expression;
8170
- }
8171
- } else {
8172
- bodyExpression = node.body;
8173
- }
8174
- } else if ((ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) && node.body) {
8175
- const returnStatement = findSingleReturnStatement(ts, node.body);
8176
- if (returnStatement?.expression) {
8177
- bodyExpression = returnStatement.expression;
8178
- }
8179
- }
8180
- if (!bodyExpression) return yield* TypeParserIssue.issue;
8181
- const { pipeArguments: pipeArguments2, subject } = yield* pipe(
8182
- typeParser.pipeCall(bodyExpression),
8183
- map8(({ args: args2, subject: subject2 }) => ({ subject: subject2, pipeArguments: args2 })),
8184
- orElse2(() => succeed({ subject: bodyExpression, pipeArguments: [] }))
8185
- );
8186
- const { effectModule, generatorFunction } = yield* typeParser.effectGen(subject);
8187
- const functionType = typeChecker.getTypeAtLocation(node);
8188
- if (!functionType) return yield* TypeParserIssue.issue;
8189
- const callSignatures = typeChecker.getSignaturesOfType(functionType, ts.SignatureKind.Call);
8190
- if (callSignatures.length !== 1) return yield* TypeParserIssue.issue;
8191
- const signature = callSignatures[0];
8192
- const returnType = typeChecker.getReturnTypeOfSignature(signature);
8193
- const { A, E, R } = yield* typeParser.strictEffectType(returnType, node);
8194
- const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
8195
- sourceFile,
8196
- "effect",
8197
- "Effect"
8198
- ) || "Effect";
8199
- const nameIdentifier = getNameIdentifier(ts, node);
8200
- const traceName = nameIdentifier ? ts.isIdentifier(nameIdentifier) ? ts.idText(nameIdentifier) : nameIdentifier.text : void 0;
8201
- const hasReturnTypeAnnotation = !!node.type;
8202
- return {
8203
- node,
8204
- nameIdentifier,
8205
- effectModule,
8206
- generatorFunction,
8207
- effectModuleName,
8208
- traceName,
8209
- hasReturnTypeAnnotation,
8210
- effectTypes: { A, E, R },
8211
- pipeArguments: pipeArguments2
8212
- };
8213
- });
8214
8329
  var effectFnOpportunity = createDiagnostic({
8215
8330
  name: "effectFnOpportunity",
8216
8331
  code: 41,
8217
- description: "Suggests using Effect.fn for functions that return Effect.gen",
8332
+ description: "Suggests using Effect.fn for functions that returns an Effect",
8218
8333
  severity: "suggestion",
8219
8334
  apply: fn("effectFnOpportunity.apply")(function* (sourceFile, report) {
8220
8335
  const ts = yield* service(TypeScriptApi);
8221
8336
  const typeChecker = yield* service(TypeCheckerApi);
8337
+ const typeCheckerUtils = yield* service(TypeCheckerUtils);
8338
+ const typeParser = yield* service(TypeParser);
8222
8339
  const tsUtils = yield* service(TypeScriptUtils);
8223
- const createReturnTypeAnnotation = (effectModuleName, effectTypes, enclosingNode) => {
8224
- const { A, E, R } = effectTypes;
8225
- const aTypeNode = typeChecker.typeToTypeNode(A, enclosingNode, ts.NodeBuilderFlags.NoTruncation);
8226
- const eTypeNode = typeChecker.typeToTypeNode(E, enclosingNode, ts.NodeBuilderFlags.NoTruncation);
8227
- const rTypeNode = typeChecker.typeToTypeNode(R, enclosingNode, ts.NodeBuilderFlags.NoTruncation);
8228
- if (!aTypeNode || !eTypeNode || !rTypeNode) return void 0;
8229
- return ts.factory.createTypeReferenceNode(
8230
- ts.factory.createQualifiedName(
8231
- ts.factory.createQualifiedName(
8232
- ts.factory.createIdentifier(effectModuleName),
8233
- "fn"
8234
- ),
8235
- "Return"
8236
- ),
8237
- [aTypeNode, eTypeNode, rTypeNode]
8340
+ const sourceEffectModuleName = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
8341
+ sourceFile,
8342
+ "effect",
8343
+ "Effect"
8344
+ ) || "Effect";
8345
+ const findSingleReturnStatement = (block) => {
8346
+ if (block.statements.length !== 1) return void 0;
8347
+ const statement = block.statements[0];
8348
+ if (!ts.isReturnStatement(statement)) return void 0;
8349
+ return statement;
8350
+ };
8351
+ const getBodyExpression = (fnNode) => {
8352
+ if (ts.isArrowFunction(fnNode)) {
8353
+ if (ts.isBlock(fnNode.body)) {
8354
+ return findSingleReturnStatement(fnNode.body)?.expression;
8355
+ }
8356
+ return fnNode.body;
8357
+ } else if ((ts.isFunctionExpression(fnNode) || ts.isFunctionDeclaration(fnNode)) && fnNode.body) {
8358
+ return findSingleReturnStatement(fnNode.body)?.expression;
8359
+ }
8360
+ return void 0;
8361
+ };
8362
+ const getNameIdentifier = (node) => {
8363
+ if (ts.isFunctionDeclaration(node) && node.name) {
8364
+ return node.name;
8365
+ }
8366
+ if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
8367
+ return node.parent.name;
8368
+ }
8369
+ if (node.parent && ts.isPropertyAssignment(node.parent)) {
8370
+ const name = node.parent.name;
8371
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
8372
+ return name;
8373
+ }
8374
+ }
8375
+ if (node.parent && ts.isPropertyDeclaration(node.parent)) {
8376
+ const name = node.parent.name;
8377
+ if (ts.isIdentifier(name)) {
8378
+ return name;
8379
+ }
8380
+ }
8381
+ return void 0;
8382
+ };
8383
+ const areParametersReferencedIn = (fnNode, nodes2) => {
8384
+ if (fnNode.parameters.length === 0 || nodes2.length === 0) return false;
8385
+ const firstParam = fnNode.parameters[0];
8386
+ const lastParam = fnNode.parameters[fnNode.parameters.length - 1];
8387
+ const paramsStart = firstParam.pos;
8388
+ const paramsEnd = lastParam.end;
8389
+ const isSymbolDeclaredInParams = (symbol3) => {
8390
+ const declarations = symbol3.declarations;
8391
+ if (!declarations) return false;
8392
+ return declarations.some((decl) => decl.pos >= paramsStart && decl.end <= paramsEnd);
8393
+ };
8394
+ const nodesToVisit = [...nodes2];
8395
+ while (nodesToVisit.length > 0) {
8396
+ const node = nodesToVisit.shift();
8397
+ if (ts.isIdentifier(node)) {
8398
+ const symbol3 = typeChecker.getSymbolAtLocation(node);
8399
+ if (symbol3 && isSymbolDeclaredInParams(symbol3)) {
8400
+ return true;
8401
+ }
8402
+ }
8403
+ if (ts.isShorthandPropertyAssignment(node)) {
8404
+ const valueSymbol = typeChecker.getShorthandAssignmentValueSymbol(node);
8405
+ if (valueSymbol && isSymbolDeclaredInParams(valueSymbol)) {
8406
+ return true;
8407
+ }
8408
+ }
8409
+ ts.forEachChild(node, (child) => {
8410
+ nodesToVisit.push(child);
8411
+ return void 0;
8412
+ });
8413
+ }
8414
+ return false;
8415
+ };
8416
+ const tryParseGenOpportunity = (fnNode) => gen(function* () {
8417
+ const bodyExpression = getBodyExpression(fnNode);
8418
+ if (!bodyExpression) return yield* TypeParserIssue.issue;
8419
+ const { pipeArguments: pipeArguments2, subject } = yield* pipe(
8420
+ typeParser.pipeCall(bodyExpression),
8421
+ map8(({ args: args2, subject: subject2 }) => ({ subject: subject2, pipeArguments: args2 })),
8422
+ orElse2(() => succeed({ subject: bodyExpression, pipeArguments: [] }))
8423
+ );
8424
+ const { effectModule, generatorFunction } = yield* typeParser.effectGen(subject);
8425
+ const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
8426
+ return { effectModuleName, generatorFunction, pipeArguments: pipeArguments2 };
8427
+ });
8428
+ const isInsideEffectFn = (fnNode) => {
8429
+ const parent = fnNode.parent;
8430
+ if (!parent || !ts.isCallExpression(parent)) {
8431
+ return succeed(false);
8432
+ }
8433
+ if (parent.arguments[0] !== fnNode) {
8434
+ return succeed(false);
8435
+ }
8436
+ return pipe(
8437
+ typeParser.effectFn(parent),
8438
+ orElse2(() => typeParser.effectFnGen(parent)),
8439
+ orElse2(() => typeParser.effectFnUntracedGen(parent)),
8440
+ map8(() => true),
8441
+ orElse2(() => succeed(false))
8238
8442
  );
8239
8443
  };
8240
- const createEffectFnNode = (originalNode, generatorFunction, effectModuleName, traceName, effectTypes, pipeArguments2) => {
8241
- const returnTypeAnnotation = effectTypes ? createReturnTypeAnnotation(effectModuleName, effectTypes, originalNode) : void 0;
8242
- const newGeneratorFunction = ts.factory.createFunctionExpression(
8444
+ const parseEffectFnOpportunityTarget = (node) => gen(function* () {
8445
+ if (!ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isFunctionDeclaration(node)) {
8446
+ return yield* TypeParserIssue.issue;
8447
+ }
8448
+ if ((ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) && node.asteriskToken) {
8449
+ return yield* TypeParserIssue.issue;
8450
+ }
8451
+ if (ts.isFunctionExpression(node) && node.name) {
8452
+ return yield* TypeParserIssue.issue;
8453
+ }
8454
+ if (yield* isInsideEffectFn(node)) {
8455
+ return yield* TypeParserIssue.issue;
8456
+ }
8457
+ const functionType = typeChecker.getTypeAtLocation(node);
8458
+ if (!functionType) return yield* TypeParserIssue.issue;
8459
+ const callSignatures = typeChecker.getSignaturesOfType(functionType, ts.SignatureKind.Call);
8460
+ if (callSignatures.length !== 1) return yield* TypeParserIssue.issue;
8461
+ const signature = callSignatures[0];
8462
+ const returnType = typeChecker.getReturnTypeOfSignature(signature);
8463
+ const unionMembers = typeCheckerUtils.unrollUnionMembers(returnType);
8464
+ yield* all(...unionMembers.map((member) => typeParser.strictEffectType(member, node)));
8465
+ const nameIdentifier = getNameIdentifier(node);
8466
+ const traceName = nameIdentifier ? ts.isIdentifier(nameIdentifier) ? ts.idText(nameIdentifier) : nameIdentifier.text : void 0;
8467
+ if (!traceName) return yield* TypeParserIssue.issue;
8468
+ const opportunity = yield* pipe(
8469
+ tryParseGenOpportunity(node),
8470
+ orElse2(() => {
8471
+ if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) {
8472
+ return TypeParserIssue.issue;
8473
+ }
8474
+ const body = ts.isArrowFunction(node) ? node.body : node.body;
8475
+ if (!body || !ts.isBlock(body) || body.statements.length <= 5) {
8476
+ return TypeParserIssue.issue;
8477
+ }
8478
+ return succeed({
8479
+ effectModuleName: sourceEffectModuleName,
8480
+ pipeArguments: [],
8481
+ generatorFunction: void 0
8482
+ });
8483
+ })
8484
+ );
8485
+ return {
8486
+ node,
8487
+ nameIdentifier,
8488
+ effectModuleName: opportunity.effectModuleName,
8489
+ traceName,
8490
+ pipeArguments: opportunity.pipeArguments,
8491
+ generatorFunction: opportunity.generatorFunction,
8492
+ hasParamsInPipeArgs: areParametersReferencedIn(node, opportunity.pipeArguments)
8493
+ };
8494
+ });
8495
+ const getFunctionBodyBlock = (node) => {
8496
+ if (ts.isArrowFunction(node)) {
8497
+ if (ts.isBlock(node.body)) {
8498
+ return node.body;
8499
+ }
8500
+ return ts.factory.createBlock([ts.factory.createReturnStatement(node.body)], true);
8501
+ }
8502
+ return node.body;
8503
+ };
8504
+ const isGeneratorFunction = (node) => {
8505
+ if (ts.isArrowFunction(node)) return false;
8506
+ return node.asteriskToken !== void 0;
8507
+ };
8508
+ const createEffectFnNode = (originalNode, innerFunction, effectModuleName, traceName, pipeArguments2) => {
8509
+ const isGenerator = isGeneratorFunction(innerFunction);
8510
+ const newFunction = ts.factory.createFunctionExpression(
8243
8511
  void 0,
8244
- ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
8512
+ isGenerator ? ts.factory.createToken(ts.SyntaxKind.AsteriskToken) : void 0,
8245
8513
  void 0,
8246
8514
  originalNode.typeParameters,
8247
8515
  originalNode.parameters,
8248
- returnTypeAnnotation,
8249
- generatorFunction.body
8516
+ void 0,
8517
+ getFunctionBodyBlock(innerFunction)
8250
8518
  );
8251
8519
  let fnExpression = ts.factory.createPropertyAccessExpression(
8252
8520
  ts.factory.createIdentifier(effectModuleName),
@@ -8259,34 +8527,27 @@ var effectFnOpportunity = createDiagnostic({
8259
8527
  [ts.factory.createStringLiteral(traceName)]
8260
8528
  );
8261
8529
  }
8262
- const effectFnCall = ts.factory.createCallExpression(
8263
- fnExpression,
8264
- void 0,
8265
- [newGeneratorFunction, ...pipeArguments2]
8266
- );
8530
+ const effectFnCall = ts.factory.createCallExpression(fnExpression, void 0, [newFunction, ...pipeArguments2]);
8267
8531
  if (ts.isFunctionDeclaration(originalNode)) {
8268
8532
  return tsUtils.tryPreserveDeclarationSemantics(originalNode, effectFnCall, false);
8269
8533
  }
8270
8534
  return effectFnCall;
8271
8535
  };
8272
- const createEffectFnUntracedNode = (originalNode, generatorFunction, effectModuleName, effectTypes, pipeArguments2) => {
8273
- const returnTypeAnnotation = effectTypes ? createReturnTypeAnnotation(effectModuleName, effectTypes, originalNode) : void 0;
8274
- const newGeneratorFunction = ts.factory.createFunctionExpression(
8536
+ const createEffectFnUntracedNode = (originalNode, innerFunction, effectModuleName, pipeArguments2) => {
8537
+ const isGenerator = isGeneratorFunction(innerFunction);
8538
+ const newFunction = ts.factory.createFunctionExpression(
8275
8539
  void 0,
8276
- ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
8540
+ isGenerator ? ts.factory.createToken(ts.SyntaxKind.AsteriskToken) : void 0,
8277
8541
  void 0,
8278
8542
  originalNode.typeParameters,
8279
8543
  originalNode.parameters,
8280
- returnTypeAnnotation,
8281
- generatorFunction.body
8544
+ void 0,
8545
+ getFunctionBodyBlock(innerFunction)
8282
8546
  );
8283
8547
  const effectFnCall = ts.factory.createCallExpression(
8284
- ts.factory.createPropertyAccessExpression(
8285
- ts.factory.createIdentifier(effectModuleName),
8286
- "fnUntraced"
8287
- ),
8548
+ ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(effectModuleName), "fnUntraced"),
8288
8549
  void 0,
8289
- [newGeneratorFunction, ...pipeArguments2]
8550
+ [newFunction, ...pipeArguments2]
8290
8551
  );
8291
8552
  if (ts.isFunctionDeclaration(originalNode)) {
8292
8553
  return tsUtils.tryPreserveDeclarationSemantics(originalNode, effectFnCall, false);
@@ -8302,88 +8563,41 @@ var effectFnOpportunity = createDiagnostic({
8302
8563
  while (nodeToVisit.length > 0) {
8303
8564
  const node = nodeToVisit.shift();
8304
8565
  ts.forEachChild(node, appendNodeToVisit);
8305
- const target = yield* pipe(
8306
- parseEffectFnOpportunityTarget(node, sourceFile),
8307
- option
8308
- );
8566
+ const target = yield* pipe(parseEffectFnOpportunityTarget(node), option);
8309
8567
  if (isNone2(target)) continue;
8310
- const {
8311
- effectModuleName,
8312
- effectTypes,
8313
- generatorFunction,
8314
- hasReturnTypeAnnotation,
8315
- nameIdentifier,
8316
- node: targetNode,
8317
- pipeArguments: pipeArguments2,
8318
- traceName
8319
- } = target.value;
8568
+ if (target.value.hasParamsInPipeArgs) continue;
8569
+ const { effectModuleName, nameIdentifier, node: targetNode, pipeArguments: pipeArguments2, traceName } = target.value;
8570
+ const innerFunction = target.value.generatorFunction ?? targetNode;
8320
8571
  const fixes = [];
8321
8572
  fixes.push({
8322
8573
  fixName: "effectFnOpportunity_toEffectFn",
8323
8574
  description: traceName ? `Convert to Effect.fn("${traceName}")` : "Convert to Effect.fn",
8324
8575
  apply: gen(function* () {
8325
8576
  const changeTracker = yield* service(ChangeTracker);
8326
- const newNode = createEffectFnNode(
8327
- targetNode,
8328
- generatorFunction,
8329
- effectModuleName,
8330
- traceName,
8331
- hasReturnTypeAnnotation ? effectTypes : void 0,
8332
- pipeArguments2
8333
- );
8334
- changeTracker.replaceNode(sourceFile, targetNode, newNode);
8335
- })
8336
- });
8337
- fixes.push({
8338
- fixName: "effectFnOpportunity_toEffectFnUntraced",
8339
- description: "Convert to Effect.fnUntraced",
8340
- apply: gen(function* () {
8341
- const changeTracker = yield* service(ChangeTracker);
8342
- const newNode = createEffectFnUntracedNode(
8343
- targetNode,
8344
- generatorFunction,
8345
- effectModuleName,
8346
- hasReturnTypeAnnotation ? effectTypes : void 0,
8347
- pipeArguments2
8348
- );
8577
+ const newNode = createEffectFnNode(targetNode, innerFunction, effectModuleName, traceName, pipeArguments2);
8349
8578
  changeTracker.replaceNode(sourceFile, targetNode, newNode);
8350
8579
  })
8351
8580
  });
8581
+ if (target.value.generatorFunction) {
8582
+ fixes.push({
8583
+ fixName: "effectFnOpportunity_toEffectFnUntraced",
8584
+ description: "Convert to Effect.fnUntraced",
8585
+ apply: gen(function* () {
8586
+ const changeTracker = yield* service(ChangeTracker);
8587
+ const newNode = createEffectFnUntracedNode(targetNode, innerFunction, effectModuleName, pipeArguments2);
8588
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
8589
+ })
8590
+ });
8591
+ }
8592
+ const pipeArgsSuffix = pipeArguments2.length > 0 ? ` Effect.fn also accepts the piped transformations as additional arguments.` : ``;
8352
8593
  report({
8353
8594
  location: nameIdentifier ?? targetNode,
8354
- messageText: `This function could benefit from Effect.fn's automatic tracing and concise syntax, or Effect.fnUntraced to get just a more concise syntax.`,
8595
+ 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}`,
8355
8596
  fixes
8356
8597
  });
8357
8598
  }
8358
8599
  })
8359
8600
  });
8360
- function findSingleReturnStatement(ts, block) {
8361
- if (block.statements.length !== 1) return void 0;
8362
- const statement = block.statements[0];
8363
- if (!ts.isReturnStatement(statement)) return void 0;
8364
- return statement;
8365
- }
8366
- function getNameIdentifier(ts, node) {
8367
- if (ts.isFunctionDeclaration(node) && node.name) {
8368
- return node.name;
8369
- }
8370
- if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
8371
- return node.parent.name;
8372
- }
8373
- if (node.parent && ts.isPropertyAssignment(node.parent)) {
8374
- const name = node.parent.name;
8375
- if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
8376
- return name;
8377
- }
8378
- }
8379
- if (node.parent && ts.isPropertyDeclaration(node.parent)) {
8380
- const name = node.parent.name;
8381
- if (ts.isIdentifier(name)) {
8382
- return name;
8383
- }
8384
- }
8385
- return void 0;
8386
- }
8387
8601
 
8388
8602
  // src/diagnostics/effectGenUsesAdapter.ts
8389
8603
  var effectGenUsesAdapter = createDiagnostic({
@@ -9146,6 +9360,52 @@ var missedPipeableOpportunity = createDiagnostic({
9146
9360
  const typeChecker = yield* service(TypeCheckerApi);
9147
9361
  const typeParser = yield* service(TypeParser);
9148
9362
  const options = yield* service(LanguageServicePluginOptions);
9363
+ const isSafelyPipeableCallee = (callee) => {
9364
+ if (ts.isCallExpression(callee)) {
9365
+ return true;
9366
+ }
9367
+ if (ts.isArrowFunction(callee)) {
9368
+ return true;
9369
+ }
9370
+ if (ts.isFunctionExpression(callee)) {
9371
+ return true;
9372
+ }
9373
+ if (ts.isParenthesizedExpression(callee)) {
9374
+ return isSafelyPipeableCallee(callee.expression);
9375
+ }
9376
+ if (ts.isIdentifier(callee)) {
9377
+ const symbol3 = typeChecker.getSymbolAtLocation(callee);
9378
+ if (!symbol3) return false;
9379
+ if (symbol3.flags & (ts.SymbolFlags.Module | ts.SymbolFlags.Namespace | ts.SymbolFlags.ValueModule)) {
9380
+ return true;
9381
+ }
9382
+ const declarations = symbol3.declarations;
9383
+ if (declarations && declarations.length > 0) {
9384
+ const decl = declarations[0];
9385
+ if (ts.isFunctionDeclaration(decl) || ts.isVariableDeclaration(decl) || ts.isImportSpecifier(decl) || ts.isImportClause(decl) || ts.isNamespaceImport(decl)) {
9386
+ return true;
9387
+ }
9388
+ }
9389
+ return false;
9390
+ }
9391
+ if (ts.isPropertyAccessExpression(callee)) {
9392
+ const subject = callee.expression;
9393
+ const symbol3 = typeChecker.getSymbolAtLocation(subject);
9394
+ if (!symbol3) return false;
9395
+ if (symbol3.flags & (ts.SymbolFlags.Module | ts.SymbolFlags.Namespace | ts.SymbolFlags.ValueModule)) {
9396
+ return true;
9397
+ }
9398
+ const declarations = symbol3.declarations;
9399
+ if (declarations && declarations.length > 0) {
9400
+ const decl = declarations[0];
9401
+ if (ts.isNamespaceImport(decl) || ts.isSourceFile(decl) || ts.isModuleDeclaration(decl)) {
9402
+ return true;
9403
+ }
9404
+ }
9405
+ return false;
9406
+ }
9407
+ return false;
9408
+ };
9149
9409
  const flows = yield* typeParser.pipingFlows(false)(sourceFile);
9150
9410
  for (const flow2 of flows) {
9151
9411
  if (flow2.transformations.length < options.pipeableMinArgCount) {
@@ -9159,76 +9419,92 @@ var missedPipeableOpportunity = createDiagnostic({
9159
9419
  if (callSigs.length > 0) {
9160
9420
  continue;
9161
9421
  }
9162
- let firstPipeableIndex = -1;
9163
- const subjectType = flow2.subject.outType;
9164
- if (!subjectType) {
9165
- continue;
9166
- }
9167
- const subjectIsPipeable = yield* pipe(
9168
- typeParser.pipeableType(subjectType, flow2.subject.node),
9169
- option
9170
- );
9171
- if (subjectIsPipeable._tag === "Some") {
9172
- firstPipeableIndex = 0;
9173
- } else {
9174
- for (let i = 0; i < flow2.transformations.length; i++) {
9422
+ const isPipeableAtIndex = function* (index) {
9423
+ if (index === 0) {
9424
+ const subjectType = flow2.subject.outType;
9425
+ if (!subjectType) return false;
9426
+ const result = yield* pipe(
9427
+ typeParser.pipeableType(subjectType, flow2.subject.node),
9428
+ option
9429
+ );
9430
+ return result._tag === "Some";
9431
+ } else {
9432
+ const t = flow2.transformations[index - 1];
9433
+ if (!t.outType) return false;
9434
+ const result = yield* pipe(
9435
+ typeParser.pipeableType(t.outType, flow2.node),
9436
+ option
9437
+ );
9438
+ return result._tag === "Some";
9439
+ }
9440
+ };
9441
+ let searchStartIndex = 0;
9442
+ while (searchStartIndex <= flow2.transformations.length) {
9443
+ let firstPipeableIndex = -1;
9444
+ for (let i = searchStartIndex; i <= flow2.transformations.length; i++) {
9445
+ if (yield* isPipeableAtIndex(i)) {
9446
+ firstPipeableIndex = i;
9447
+ break;
9448
+ }
9449
+ }
9450
+ if (firstPipeableIndex === -1) {
9451
+ break;
9452
+ }
9453
+ const pipeableTransformations = [];
9454
+ for (let i = firstPipeableIndex; i < flow2.transformations.length; i++) {
9175
9455
  const t = flow2.transformations[i];
9176
- if (t.outType) {
9177
- const isPipeable = yield* pipe(
9178
- typeParser.pipeableType(t.outType, flow2.node),
9179
- option
9180
- );
9181
- if (isPipeable._tag === "Some") {
9182
- firstPipeableIndex = i + 1;
9183
- break;
9184
- }
9456
+ if (!isSafelyPipeableCallee(t.callee)) {
9457
+ break;
9185
9458
  }
9459
+ pipeableTransformations.push(t);
9186
9460
  }
9187
- }
9188
- if (firstPipeableIndex === -1) {
9189
- continue;
9190
- }
9191
- const transformationsAfterPipeable = flow2.transformations.slice(firstPipeableIndex);
9192
- const callKindCount = transformationsAfterPipeable.filter((t) => t.kind === "call").length;
9193
- if (callKindCount < options.pipeableMinArgCount) {
9194
- continue;
9195
- }
9196
- const pipeableSubjectNode = firstPipeableIndex === 0 ? flow2.subject.node : typeParser.reconstructPipingFlow({
9197
- subject: flow2.subject,
9198
- transformations: flow2.transformations.slice(0, firstPipeableIndex)
9199
- });
9200
- const pipeableTransformations = flow2.transformations.slice(firstPipeableIndex);
9201
- report({
9202
- location: flow2.node,
9203
- messageText: `Nested function calls can be converted to pipeable style for better readability.`,
9204
- fixes: [{
9205
- fixName: "missedPipeableOpportunity_fix",
9206
- description: "Convert to pipe style",
9207
- apply: gen(function* () {
9208
- const changeTracker = yield* service(ChangeTracker);
9209
- const pipeArgs = pipeableTransformations.map((t) => {
9210
- if (t.args) {
9211
- return ts.factory.createCallExpression(
9212
- t.callee,
9461
+ const callKindCount = pipeableTransformations.filter((t) => t.kind === "call").length;
9462
+ if (callKindCount >= options.pipeableMinArgCount) {
9463
+ const pipeableEndIndex = firstPipeableIndex + pipeableTransformations.length;
9464
+ const pipeableSubjectNode = firstPipeableIndex === 0 ? flow2.subject.node : typeParser.reconstructPipingFlow({
9465
+ subject: flow2.subject,
9466
+ transformations: flow2.transformations.slice(0, firstPipeableIndex)
9467
+ });
9468
+ const afterTransformations = flow2.transformations.slice(pipeableEndIndex);
9469
+ report({
9470
+ location: flow2.node,
9471
+ messageText: `Nested function calls can be converted to pipeable style for better readability.`,
9472
+ fixes: [{
9473
+ fixName: "missedPipeableOpportunity_fix",
9474
+ description: "Convert to pipe style",
9475
+ apply: gen(function* () {
9476
+ const changeTracker = yield* service(ChangeTracker);
9477
+ const pipeArgs = pipeableTransformations.map((t) => {
9478
+ if (t.args) {
9479
+ return ts.factory.createCallExpression(
9480
+ t.callee,
9481
+ void 0,
9482
+ t.args
9483
+ );
9484
+ } else {
9485
+ return t.callee;
9486
+ }
9487
+ });
9488
+ const pipeNode = ts.factory.createCallExpression(
9489
+ ts.factory.createPropertyAccessExpression(
9490
+ pipeableSubjectNode,
9491
+ "pipe"
9492
+ ),
9213
9493
  void 0,
9214
- t.args
9494
+ pipeArgs
9215
9495
  );
9216
- } else {
9217
- return t.callee;
9218
- }
9219
- });
9220
- const newNode = ts.factory.createCallExpression(
9221
- ts.factory.createPropertyAccessExpression(
9222
- pipeableSubjectNode,
9223
- "pipe"
9224
- ),
9225
- void 0,
9226
- pipeArgs
9227
- );
9228
- changeTracker.replaceNode(sourceFile, flow2.node, newNode);
9229
- })
9230
- }]
9231
- });
9496
+ const newNode = afterTransformations.length > 0 ? typeParser.reconstructPipingFlow({
9497
+ subject: { node: pipeNode, outType: void 0 },
9498
+ transformations: afterTransformations
9499
+ }) : pipeNode;
9500
+ changeTracker.replaceNode(sourceFile, flow2.node, newNode);
9501
+ })
9502
+ }]
9503
+ });
9504
+ break;
9505
+ }
9506
+ searchStartIndex = firstPipeableIndex + pipeableTransformations.length + 1;
9507
+ }
9232
9508
  }
9233
9509
  })
9234
9510
  });
@@ -10074,6 +10350,90 @@ var overriddenSchemaConstructor = createDiagnostic({
10074
10350
  })
10075
10351
  });
10076
10352
 
10353
+ // src/diagnostics/preferSchemaOverJson.ts
10354
+ var preferSchemaOverJson = createDiagnostic({
10355
+ name: "preferSchemaOverJson",
10356
+ code: 44,
10357
+ description: "Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify which may throw",
10358
+ severity: "suggestion",
10359
+ apply: fn("preferSchemaOverJson.apply")(function* (sourceFile, report) {
10360
+ const ts = yield* service(TypeScriptApi);
10361
+ const typeParser = yield* service(TypeParser);
10362
+ const parseJsonMethod = (node) => gen(function* () {
10363
+ if (!ts.isCallExpression(node)) return yield* fail3("node is not a call expression");
10364
+ const expression = node.expression;
10365
+ if (!ts.isPropertyAccessExpression(expression)) return yield* fail3("expression is not a property access");
10366
+ const objectExpr = expression.expression;
10367
+ const methodName = ts.idText(expression.name);
10368
+ if (!ts.isIdentifier(objectExpr) || ts.idText(objectExpr) !== "JSON") {
10369
+ return yield* fail3("object is not JSON");
10370
+ }
10371
+ if (methodName !== "parse" && methodName !== "stringify") {
10372
+ return yield* fail3("method is not parse or stringify");
10373
+ }
10374
+ return { node, methodName };
10375
+ });
10376
+ const effectTrySimple = (node) => gen(function* () {
10377
+ if (!ts.isCallExpression(node)) return yield* fail3("node is not a call expression");
10378
+ yield* typeParser.isNodeReferenceToEffectModuleApi("try")(node.expression);
10379
+ if (node.arguments.length === 0) return yield* fail3("Effect.try has no arguments");
10380
+ const lazyFn = yield* typeParser.lazyExpression(node.arguments[0]);
10381
+ const jsonMethod = yield* parseJsonMethod(lazyFn.expression);
10382
+ return { node: jsonMethod.node, methodName: jsonMethod.methodName };
10383
+ });
10384
+ const effectTryObject = (node) => gen(function* () {
10385
+ if (!ts.isCallExpression(node)) return yield* fail3("node is not a call expression");
10386
+ yield* typeParser.isNodeReferenceToEffectModuleApi("try")(node.expression);
10387
+ if (node.arguments.length === 0) return yield* fail3("Effect.try has no arguments");
10388
+ const arg = node.arguments[0];
10389
+ if (!ts.isObjectLiteralExpression(arg)) return yield* fail3("argument is not an object literal");
10390
+ const tryProp = arg.properties.find(
10391
+ (p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && ts.idText(p.name) === "try"
10392
+ );
10393
+ if (!tryProp) return yield* fail3("object has no 'try' property");
10394
+ const lazyFn = yield* typeParser.lazyExpression(tryProp.initializer);
10395
+ const jsonMethod = yield* parseJsonMethod(lazyFn.expression);
10396
+ return { node: jsonMethod.node, methodName: jsonMethod.methodName };
10397
+ });
10398
+ const jsonMethodInEffectGen = (node) => gen(function* () {
10399
+ const jsonMethod = yield* parseJsonMethod(node);
10400
+ const { effectGen, scopeNode } = yield* typeParser.findEnclosingScopes(node);
10401
+ if (!effectGen || effectGen.body.statements.length === 0) {
10402
+ return yield* fail3("not inside an Effect generator");
10403
+ }
10404
+ if (scopeNode && scopeNode !== effectGen.generatorFunction) {
10405
+ return yield* fail3("inside a nested function scope");
10406
+ }
10407
+ return { node: jsonMethod.node, methodName: jsonMethod.methodName };
10408
+ });
10409
+ const nodeToVisit = [];
10410
+ const appendNodeToVisit = (node) => {
10411
+ nodeToVisit.push(node);
10412
+ return void 0;
10413
+ };
10414
+ ts.forEachChild(sourceFile, appendNodeToVisit);
10415
+ while (nodeToVisit.length > 0) {
10416
+ const node = nodeToVisit.shift();
10417
+ ts.forEachChild(node, appendNodeToVisit);
10418
+ const match3 = yield* pipe(
10419
+ firstSuccessOf([
10420
+ effectTrySimple(node),
10421
+ effectTryObject(node),
10422
+ jsonMethodInEffectGen(node)
10423
+ ]),
10424
+ option
10425
+ );
10426
+ if (isSome2(match3)) {
10427
+ report({
10428
+ location: match3.value.node,
10429
+ messageText: "Consider using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify",
10430
+ fixes: []
10431
+ });
10432
+ }
10433
+ }
10434
+ })
10435
+ });
10436
+
10077
10437
  // src/diagnostics/redundantSchemaTagIdentifier.ts
10078
10438
  var redundantSchemaTagIdentifier = createDiagnostic({
10079
10439
  name: "redundantSchemaTagIdentifier",
@@ -10226,108 +10586,91 @@ var runEffectInsideEffect = createDiagnostic({
10226
10586
  option
10227
10587
  );
10228
10588
  if (isNone2(isEffectRunCall)) continue;
10229
- let currentParent = node.parent;
10230
- let nodeIntroduceScope = void 0;
10231
- while (currentParent) {
10232
- const possiblyEffectGen = currentParent;
10233
- if (!nodeIntroduceScope) {
10234
- if (ts.isFunctionExpression(possiblyEffectGen) || ts.isFunctionDeclaration(possiblyEffectGen) || ts.isMethodDeclaration(possiblyEffectGen) || ts.isArrowFunction(possiblyEffectGen)) {
10235
- nodeIntroduceScope = possiblyEffectGen;
10236
- continue;
10237
- }
10238
- }
10239
- const isInEffectGen = yield* pipe(
10240
- typeParser.effectGen(possiblyEffectGen),
10241
- orElse2(() => typeParser.effectFnUntracedGen(possiblyEffectGen)),
10242
- orElse2(() => typeParser.effectFnGen(possiblyEffectGen)),
10243
- option
10589
+ const { effectGen, scopeNode } = yield* typeParser.findEnclosingScopes(node);
10590
+ if (effectGen && effectGen.body.statements.length > 0) {
10591
+ const nodeText = sourceFile.text.substring(
10592
+ ts.getTokenPosOfNode(node.expression, sourceFile),
10593
+ node.expression.end
10244
10594
  );
10245
- if (isSome2(isInEffectGen) && isInEffectGen.value.body.statements.length > 0) {
10246
- const nodeText = sourceFile.text.substring(
10247
- ts.getTokenPosOfNode(node.expression, sourceFile),
10248
- node.expression.end
10249
- );
10250
- if (nodeIntroduceScope && nodeIntroduceScope !== isInEffectGen.value.generatorFunction) {
10251
- const fixAddRuntime = gen(function* () {
10252
- const changeTracker = yield* service(ChangeTracker);
10253
- const runtimeModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Runtime") || "Runtime";
10254
- const effectModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect") || "Effect";
10255
- let runtimeIdentifier = void 0;
10256
- for (const statement of isInEffectGen.value.generatorFunction.body.statements) {
10257
- if (ts.isVariableStatement(statement) && statement.declarationList.declarations.length === 1) {
10258
- const declaration = statement.declarationList.declarations[0];
10259
- if (declaration.initializer && ts.isYieldExpression(declaration.initializer) && declaration.initializer.asteriskToken && declaration.initializer.expression) {
10260
- const yieldedExpression = declaration.initializer.expression;
10261
- if (ts.isCallExpression(yieldedExpression)) {
10262
- const maybeEffectRuntime = yield* pipe(
10263
- typeParser.isNodeReferenceToEffectModuleApi("runtime")(yieldedExpression.expression),
10264
- option
10265
- );
10266
- if (isSome2(maybeEffectRuntime) && ts.isIdentifier(declaration.name)) {
10267
- runtimeIdentifier = ts.idText(declaration.name);
10268
- }
10595
+ if (scopeNode && scopeNode !== effectGen.generatorFunction) {
10596
+ const fixAddRuntime = gen(function* () {
10597
+ const changeTracker = yield* service(ChangeTracker);
10598
+ const runtimeModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Runtime") || "Runtime";
10599
+ const effectModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect") || "Effect";
10600
+ let runtimeIdentifier = void 0;
10601
+ for (const statement of effectGen.generatorFunction.body.statements) {
10602
+ if (ts.isVariableStatement(statement) && statement.declarationList.declarations.length === 1) {
10603
+ const declaration = statement.declarationList.declarations[0];
10604
+ if (declaration.initializer && ts.isYieldExpression(declaration.initializer) && declaration.initializer.asteriskToken && declaration.initializer.expression) {
10605
+ const yieldedExpression = declaration.initializer.expression;
10606
+ if (ts.isCallExpression(yieldedExpression)) {
10607
+ const maybeEffectRuntime = yield* pipe(
10608
+ typeParser.isNodeReferenceToEffectModuleApi("runtime")(yieldedExpression.expression),
10609
+ option
10610
+ );
10611
+ if (isSome2(maybeEffectRuntime) && ts.isIdentifier(declaration.name)) {
10612
+ runtimeIdentifier = ts.idText(declaration.name);
10269
10613
  }
10270
10614
  }
10271
10615
  }
10272
10616
  }
10273
- if (!runtimeIdentifier) {
10274
- changeTracker.insertNodeAt(
10275
- sourceFile,
10276
- isInEffectGen.value.body.statements[0].pos,
10277
- ts.factory.createVariableStatement(
10617
+ }
10618
+ if (!runtimeIdentifier) {
10619
+ changeTracker.insertNodeAt(
10620
+ sourceFile,
10621
+ effectGen.body.statements[0].pos,
10622
+ ts.factory.createVariableStatement(
10623
+ void 0,
10624
+ ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(
10625
+ "effectRuntime",
10278
10626
  void 0,
10279
- ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(
10280
- "effectRuntime",
10281
- void 0,
10282
- void 0,
10283
- ts.factory.createYieldExpression(
10284
- ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
10285
- ts.factory.createCallExpression(
10286
- ts.factory.createPropertyAccessExpression(
10287
- ts.factory.createIdentifier(effectModuleIdentifier),
10288
- "runtime"
10289
- ),
10290
- [ts.factory.createTypeReferenceNode("never")],
10291
- []
10292
- )
10627
+ void 0,
10628
+ ts.factory.createYieldExpression(
10629
+ ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
10630
+ ts.factory.createCallExpression(
10631
+ ts.factory.createPropertyAccessExpression(
10632
+ ts.factory.createIdentifier(effectModuleIdentifier),
10633
+ "runtime"
10634
+ ),
10635
+ [ts.factory.createTypeReferenceNode("never")],
10636
+ []
10293
10637
  )
10294
- )], ts.NodeFlags.Const)
10295
- ),
10296
- {
10297
- prefix: "\n",
10298
- suffix: "\n"
10299
- }
10300
- );
10301
- }
10302
- changeTracker.deleteRange(sourceFile, {
10303
- pos: ts.getTokenPosOfNode(node.expression, sourceFile),
10304
- end: node.arguments[0].pos
10305
- });
10306
- changeTracker.insertText(
10307
- sourceFile,
10308
- node.arguments[0].pos,
10309
- `${runtimeModuleIdentifier}.${isEffectRunCall.value.methodName}(${runtimeIdentifier || "effectRuntime"}, `
10638
+ )
10639
+ )], ts.NodeFlags.Const)
10640
+ ),
10641
+ {
10642
+ prefix: "\n",
10643
+ suffix: "\n"
10644
+ }
10310
10645
  );
10646
+ }
10647
+ changeTracker.deleteRange(sourceFile, {
10648
+ pos: ts.getTokenPosOfNode(node.expression, sourceFile),
10649
+ end: node.arguments[0].pos
10311
10650
  });
10312
- report({
10313
- location: node.expression,
10314
- messageText: `Using ${nodeText} inside an Effect is not recommended. The same runtime should generally be used instead to run child effects.
10651
+ changeTracker.insertText(
10652
+ sourceFile,
10653
+ node.arguments[0].pos,
10654
+ `${runtimeModuleIdentifier}.${isEffectRunCall.value.methodName}(${runtimeIdentifier || "effectRuntime"}, `
10655
+ );
10656
+ });
10657
+ report({
10658
+ location: node.expression,
10659
+ messageText: `Using ${nodeText} inside an Effect is not recommended. The same runtime should generally be used instead to run child effects.
10315
10660
  Consider extracting the Runtime by using for example Effect.runtime and then use Runtime.${isEffectRunCall.value.methodName} with the extracted runtime instead.`,
10316
- fixes: [{
10317
- fixName: "runEffectInsideEffect_fix",
10318
- description: "Use a runtime to run the Effect",
10319
- apply: fixAddRuntime
10320
- }]
10321
- });
10322
- } else {
10323
- report({
10324
- location: node.expression,
10325
- messageText: `Using ${nodeText} inside an Effect is not recommended. Effects inside generators can usually just be yielded.`,
10326
- fixes: []
10327
- });
10328
- }
10661
+ fixes: [{
10662
+ fixName: "runEffectInsideEffect_fix",
10663
+ description: "Use a runtime to run the Effect",
10664
+ apply: fixAddRuntime
10665
+ }]
10666
+ });
10667
+ } else {
10668
+ report({
10669
+ location: node.expression,
10670
+ messageText: `Using ${nodeText} inside an Effect is not recommended. Effects inside generators can usually just be yielded.`,
10671
+ fixes: []
10672
+ });
10329
10673
  }
10330
- currentParent = currentParent.parent;
10331
10674
  }
10332
10675
  }
10333
10676
  })
@@ -10413,6 +10756,59 @@ var schemaStructWithTag = createDiagnostic({
10413
10756
  })
10414
10757
  });
10415
10758
 
10759
+ // src/diagnostics/schemaSyncInEffect.ts
10760
+ var syncToEffectMethod = {
10761
+ decodeSync: "decode",
10762
+ decodeUnknownSync: "decodeUnknown",
10763
+ encodeSync: "encode",
10764
+ encodeUnknownSync: "encodeUnknown"
10765
+ };
10766
+ var schemaSyncInEffect = createDiagnostic({
10767
+ name: "schemaSyncInEffect",
10768
+ code: 43,
10769
+ description: "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
10770
+ severity: "suggestion",
10771
+ apply: fn("schemaSyncInEffect.apply")(function* (sourceFile, report) {
10772
+ const ts = yield* service(TypeScriptApi);
10773
+ const typeParser = yield* service(TypeParser);
10774
+ const parseSchemaSyncMethod = (node, methodName) => pipe(
10775
+ typeParser.isNodeReferenceToEffectParseResultModuleApi(methodName)(node),
10776
+ map8(() => ({ node, methodName }))
10777
+ );
10778
+ const nodeToVisit = [];
10779
+ const appendNodeToVisit = (node) => {
10780
+ nodeToVisit.push(node);
10781
+ return void 0;
10782
+ };
10783
+ ts.forEachChild(sourceFile, appendNodeToVisit);
10784
+ while (nodeToVisit.length > 0) {
10785
+ const node = nodeToVisit.shift();
10786
+ ts.forEachChild(node, appendNodeToVisit);
10787
+ if (!ts.isCallExpression(node)) continue;
10788
+ const isSchemaSyncCall = yield* pipe(
10789
+ firstSuccessOf(
10790
+ Object.keys(syncToEffectMethod).map((methodName) => parseSchemaSyncMethod(node.expression, methodName))
10791
+ ),
10792
+ option
10793
+ );
10794
+ if (isNone2(isSchemaSyncCall)) continue;
10795
+ const { effectGen, scopeNode } = yield* typeParser.findEnclosingScopes(node);
10796
+ if (!effectGen || effectGen.body.statements.length === 0) continue;
10797
+ if (scopeNode && scopeNode !== effectGen.generatorFunction) continue;
10798
+ const nodeText = sourceFile.text.substring(
10799
+ ts.getTokenPosOfNode(node.expression, sourceFile),
10800
+ node.expression.end
10801
+ );
10802
+ const effectMethodName = syncToEffectMethod[isSchemaSyncCall.value.methodName];
10803
+ report({
10804
+ location: node.expression,
10805
+ messageText: `Using ${nodeText} inside an Effect generator is not recommended. Use Schema.${effectMethodName} instead to get properly typed ParseError in the error channel.`,
10806
+ fixes: []
10807
+ });
10808
+ }
10809
+ })
10810
+ });
10811
+
10416
10812
  // src/diagnostics/schemaUnionOfLiterals.ts
10417
10813
  var schemaUnionOfLiterals = createDiagnostic({
10418
10814
  name: "schemaUnionOfLiterals",
@@ -11127,7 +11523,9 @@ var diagnostics = [
11127
11523
  layerMergeAllWithDependencies,
11128
11524
  effectMapVoid,
11129
11525
  effectFnOpportunity,
11130
- redundantSchemaTagIdentifier
11526
+ redundantSchemaTagIdentifier,
11527
+ schemaSyncInEffect,
11528
+ preferSchemaOverJson
11131
11529
  ];
11132
11530
 
11133
11531
  // src/completions/effectDiagnosticsComment.ts