@effect/language-service 0.65.0 → 0.66.1

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,184 @@ ${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: [] }))
8238
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))
8442
+ );
8443
+ };
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
+ () => succeed({ effectModuleName: sourceEffectModuleName, pipeArguments: [], generatorFunction: void 0 })
8472
+ )
8473
+ );
8474
+ return {
8475
+ node,
8476
+ nameIdentifier,
8477
+ effectModuleName: opportunity.effectModuleName,
8478
+ traceName,
8479
+ pipeArguments: opportunity.pipeArguments,
8480
+ generatorFunction: opportunity.generatorFunction,
8481
+ hasParamsInPipeArgs: areParametersReferencedIn(node, opportunity.pipeArguments)
8482
+ };
8483
+ });
8484
+ const getFunctionBodyBlock = (node) => {
8485
+ if (ts.isArrowFunction(node)) {
8486
+ if (ts.isBlock(node.body)) {
8487
+ return node.body;
8488
+ }
8489
+ return ts.factory.createBlock([ts.factory.createReturnStatement(node.body)], true);
8490
+ }
8491
+ return node.body;
8239
8492
  };
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(
8493
+ const isGeneratorFunction = (node) => {
8494
+ if (ts.isArrowFunction(node)) return false;
8495
+ return node.asteriskToken !== void 0;
8496
+ };
8497
+ const createEffectFnNode = (originalNode, innerFunction, effectModuleName, traceName, pipeArguments2) => {
8498
+ const isGenerator = isGeneratorFunction(innerFunction);
8499
+ const newFunction = ts.factory.createFunctionExpression(
8243
8500
  void 0,
8244
- ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
8501
+ isGenerator ? ts.factory.createToken(ts.SyntaxKind.AsteriskToken) : void 0,
8245
8502
  void 0,
8246
8503
  originalNode.typeParameters,
8247
8504
  originalNode.parameters,
8248
- returnTypeAnnotation,
8249
- generatorFunction.body
8505
+ void 0,
8506
+ getFunctionBodyBlock(innerFunction)
8250
8507
  );
8251
8508
  let fnExpression = ts.factory.createPropertyAccessExpression(
8252
8509
  ts.factory.createIdentifier(effectModuleName),
@@ -8259,34 +8516,27 @@ var effectFnOpportunity = createDiagnostic({
8259
8516
  [ts.factory.createStringLiteral(traceName)]
8260
8517
  );
8261
8518
  }
8262
- const effectFnCall = ts.factory.createCallExpression(
8263
- fnExpression,
8264
- void 0,
8265
- [newGeneratorFunction, ...pipeArguments2]
8266
- );
8519
+ const effectFnCall = ts.factory.createCallExpression(fnExpression, void 0, [newFunction, ...pipeArguments2]);
8267
8520
  if (ts.isFunctionDeclaration(originalNode)) {
8268
8521
  return tsUtils.tryPreserveDeclarationSemantics(originalNode, effectFnCall, false);
8269
8522
  }
8270
8523
  return effectFnCall;
8271
8524
  };
8272
- const createEffectFnUntracedNode = (originalNode, generatorFunction, effectModuleName, effectTypes, pipeArguments2) => {
8273
- const returnTypeAnnotation = effectTypes ? createReturnTypeAnnotation(effectModuleName, effectTypes, originalNode) : void 0;
8274
- const newGeneratorFunction = ts.factory.createFunctionExpression(
8525
+ const createEffectFnUntracedNode = (originalNode, innerFunction, effectModuleName, pipeArguments2) => {
8526
+ const isGenerator = isGeneratorFunction(innerFunction);
8527
+ const newFunction = ts.factory.createFunctionExpression(
8275
8528
  void 0,
8276
- ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
8529
+ isGenerator ? ts.factory.createToken(ts.SyntaxKind.AsteriskToken) : void 0,
8277
8530
  void 0,
8278
8531
  originalNode.typeParameters,
8279
8532
  originalNode.parameters,
8280
- returnTypeAnnotation,
8281
- generatorFunction.body
8533
+ void 0,
8534
+ getFunctionBodyBlock(innerFunction)
8282
8535
  );
8283
8536
  const effectFnCall = ts.factory.createCallExpression(
8284
- ts.factory.createPropertyAccessExpression(
8285
- ts.factory.createIdentifier(effectModuleName),
8286
- "fnUntraced"
8287
- ),
8537
+ ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(effectModuleName), "fnUntraced"),
8288
8538
  void 0,
8289
- [newGeneratorFunction, ...pipeArguments2]
8539
+ [newFunction, ...pipeArguments2]
8290
8540
  );
8291
8541
  if (ts.isFunctionDeclaration(originalNode)) {
8292
8542
  return tsUtils.tryPreserveDeclarationSemantics(originalNode, effectFnCall, false);
@@ -8302,88 +8552,41 @@ var effectFnOpportunity = createDiagnostic({
8302
8552
  while (nodeToVisit.length > 0) {
8303
8553
  const node = nodeToVisit.shift();
8304
8554
  ts.forEachChild(node, appendNodeToVisit);
8305
- const target = yield* pipe(
8306
- parseEffectFnOpportunityTarget(node, sourceFile),
8307
- option
8308
- );
8555
+ const target = yield* pipe(parseEffectFnOpportunityTarget(node), option);
8309
8556
  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;
8557
+ if (target.value.hasParamsInPipeArgs) continue;
8558
+ const { effectModuleName, nameIdentifier, node: targetNode, pipeArguments: pipeArguments2, traceName } = target.value;
8559
+ const innerFunction = target.value.generatorFunction ?? targetNode;
8320
8560
  const fixes = [];
8321
8561
  fixes.push({
8322
8562
  fixName: "effectFnOpportunity_toEffectFn",
8323
8563
  description: traceName ? `Convert to Effect.fn("${traceName}")` : "Convert to Effect.fn",
8324
8564
  apply: gen(function* () {
8325
8565
  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
- );
8566
+ const newNode = createEffectFnNode(targetNode, innerFunction, effectModuleName, traceName, pipeArguments2);
8349
8567
  changeTracker.replaceNode(sourceFile, targetNode, newNode);
8350
8568
  })
8351
8569
  });
8570
+ if (target.value.generatorFunction) {
8571
+ fixes.push({
8572
+ fixName: "effectFnOpportunity_toEffectFnUntraced",
8573
+ description: "Convert to Effect.fnUntraced",
8574
+ apply: gen(function* () {
8575
+ const changeTracker = yield* service(ChangeTracker);
8576
+ const newNode = createEffectFnUntracedNode(targetNode, innerFunction, effectModuleName, pipeArguments2);
8577
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
8578
+ })
8579
+ });
8580
+ }
8581
+ const pipeArgsSuffix = pipeArguments2.length > 0 ? ` Effect.fn also accepts the piped transformations as additional arguments.` : ``;
8352
8582
  report({
8353
8583
  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.`,
8584
+ 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
8585
  fixes
8356
8586
  });
8357
8587
  }
8358
8588
  })
8359
8589
  });
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
8590
 
8388
8591
  // src/diagnostics/effectGenUsesAdapter.ts
8389
8592
  var effectGenUsesAdapter = createDiagnostic({
@@ -9146,6 +9349,52 @@ var missedPipeableOpportunity = createDiagnostic({
9146
9349
  const typeChecker = yield* service(TypeCheckerApi);
9147
9350
  const typeParser = yield* service(TypeParser);
9148
9351
  const options = yield* service(LanguageServicePluginOptions);
9352
+ const isSafelyPipeableCallee = (callee) => {
9353
+ if (ts.isCallExpression(callee)) {
9354
+ return true;
9355
+ }
9356
+ if (ts.isArrowFunction(callee)) {
9357
+ return true;
9358
+ }
9359
+ if (ts.isFunctionExpression(callee)) {
9360
+ return true;
9361
+ }
9362
+ if (ts.isParenthesizedExpression(callee)) {
9363
+ return isSafelyPipeableCallee(callee.expression);
9364
+ }
9365
+ if (ts.isIdentifier(callee)) {
9366
+ const symbol3 = typeChecker.getSymbolAtLocation(callee);
9367
+ if (!symbol3) return false;
9368
+ if (symbol3.flags & (ts.SymbolFlags.Module | ts.SymbolFlags.Namespace | ts.SymbolFlags.ValueModule)) {
9369
+ return true;
9370
+ }
9371
+ const declarations = symbol3.declarations;
9372
+ if (declarations && declarations.length > 0) {
9373
+ const decl = declarations[0];
9374
+ if (ts.isFunctionDeclaration(decl) || ts.isVariableDeclaration(decl) || ts.isImportSpecifier(decl) || ts.isImportClause(decl) || ts.isNamespaceImport(decl)) {
9375
+ return true;
9376
+ }
9377
+ }
9378
+ return false;
9379
+ }
9380
+ if (ts.isPropertyAccessExpression(callee)) {
9381
+ const subject = callee.expression;
9382
+ const symbol3 = typeChecker.getSymbolAtLocation(subject);
9383
+ if (!symbol3) return false;
9384
+ if (symbol3.flags & (ts.SymbolFlags.Module | ts.SymbolFlags.Namespace | ts.SymbolFlags.ValueModule)) {
9385
+ return true;
9386
+ }
9387
+ const declarations = symbol3.declarations;
9388
+ if (declarations && declarations.length > 0) {
9389
+ const decl = declarations[0];
9390
+ if (ts.isNamespaceImport(decl) || ts.isSourceFile(decl) || ts.isModuleDeclaration(decl)) {
9391
+ return true;
9392
+ }
9393
+ }
9394
+ return false;
9395
+ }
9396
+ return false;
9397
+ };
9149
9398
  const flows = yield* typeParser.pipingFlows(false)(sourceFile);
9150
9399
  for (const flow2 of flows) {
9151
9400
  if (flow2.transformations.length < options.pipeableMinArgCount) {
@@ -9159,76 +9408,92 @@ var missedPipeableOpportunity = createDiagnostic({
9159
9408
  if (callSigs.length > 0) {
9160
9409
  continue;
9161
9410
  }
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++) {
9411
+ const isPipeableAtIndex = function* (index) {
9412
+ if (index === 0) {
9413
+ const subjectType = flow2.subject.outType;
9414
+ if (!subjectType) return false;
9415
+ const result = yield* pipe(
9416
+ typeParser.pipeableType(subjectType, flow2.subject.node),
9417
+ option
9418
+ );
9419
+ return result._tag === "Some";
9420
+ } else {
9421
+ const t = flow2.transformations[index - 1];
9422
+ if (!t.outType) return false;
9423
+ const result = yield* pipe(
9424
+ typeParser.pipeableType(t.outType, flow2.node),
9425
+ option
9426
+ );
9427
+ return result._tag === "Some";
9428
+ }
9429
+ };
9430
+ let searchStartIndex = 0;
9431
+ while (searchStartIndex <= flow2.transformations.length) {
9432
+ let firstPipeableIndex = -1;
9433
+ for (let i = searchStartIndex; i <= flow2.transformations.length; i++) {
9434
+ if (yield* isPipeableAtIndex(i)) {
9435
+ firstPipeableIndex = i;
9436
+ break;
9437
+ }
9438
+ }
9439
+ if (firstPipeableIndex === -1) {
9440
+ break;
9441
+ }
9442
+ const pipeableTransformations = [];
9443
+ for (let i = firstPipeableIndex; i < flow2.transformations.length; i++) {
9175
9444
  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
- }
9445
+ if (!isSafelyPipeableCallee(t.callee)) {
9446
+ break;
9185
9447
  }
9448
+ pipeableTransformations.push(t);
9186
9449
  }
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,
9450
+ const callKindCount = pipeableTransformations.filter((t) => t.kind === "call").length;
9451
+ if (callKindCount >= options.pipeableMinArgCount) {
9452
+ const pipeableEndIndex = firstPipeableIndex + pipeableTransformations.length;
9453
+ const pipeableSubjectNode = firstPipeableIndex === 0 ? flow2.subject.node : typeParser.reconstructPipingFlow({
9454
+ subject: flow2.subject,
9455
+ transformations: flow2.transformations.slice(0, firstPipeableIndex)
9456
+ });
9457
+ const afterTransformations = flow2.transformations.slice(pipeableEndIndex);
9458
+ report({
9459
+ location: flow2.node,
9460
+ messageText: `Nested function calls can be converted to pipeable style for better readability.`,
9461
+ fixes: [{
9462
+ fixName: "missedPipeableOpportunity_fix",
9463
+ description: "Convert to pipe style",
9464
+ apply: gen(function* () {
9465
+ const changeTracker = yield* service(ChangeTracker);
9466
+ const pipeArgs = pipeableTransformations.map((t) => {
9467
+ if (t.args) {
9468
+ return ts.factory.createCallExpression(
9469
+ t.callee,
9470
+ void 0,
9471
+ t.args
9472
+ );
9473
+ } else {
9474
+ return t.callee;
9475
+ }
9476
+ });
9477
+ const pipeNode = ts.factory.createCallExpression(
9478
+ ts.factory.createPropertyAccessExpression(
9479
+ pipeableSubjectNode,
9480
+ "pipe"
9481
+ ),
9213
9482
  void 0,
9214
- t.args
9483
+ pipeArgs
9215
9484
  );
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
- });
9485
+ const newNode = afterTransformations.length > 0 ? typeParser.reconstructPipingFlow({
9486
+ subject: { node: pipeNode, outType: void 0 },
9487
+ transformations: afterTransformations
9488
+ }) : pipeNode;
9489
+ changeTracker.replaceNode(sourceFile, flow2.node, newNode);
9490
+ })
9491
+ }]
9492
+ });
9493
+ break;
9494
+ }
9495
+ searchStartIndex = firstPipeableIndex + pipeableTransformations.length + 1;
9496
+ }
9232
9497
  }
9233
9498
  })
9234
9499
  });
@@ -10074,6 +10339,90 @@ var overriddenSchemaConstructor = createDiagnostic({
10074
10339
  })
10075
10340
  });
10076
10341
 
10342
+ // src/diagnostics/preferSchemaOverJson.ts
10343
+ var preferSchemaOverJson = createDiagnostic({
10344
+ name: "preferSchemaOverJson",
10345
+ code: 44,
10346
+ description: "Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify which may throw",
10347
+ severity: "suggestion",
10348
+ apply: fn("preferSchemaOverJson.apply")(function* (sourceFile, report) {
10349
+ const ts = yield* service(TypeScriptApi);
10350
+ const typeParser = yield* service(TypeParser);
10351
+ const parseJsonMethod = (node) => gen(function* () {
10352
+ if (!ts.isCallExpression(node)) return yield* fail3("node is not a call expression");
10353
+ const expression = node.expression;
10354
+ if (!ts.isPropertyAccessExpression(expression)) return yield* fail3("expression is not a property access");
10355
+ const objectExpr = expression.expression;
10356
+ const methodName = ts.idText(expression.name);
10357
+ if (!ts.isIdentifier(objectExpr) || ts.idText(objectExpr) !== "JSON") {
10358
+ return yield* fail3("object is not JSON");
10359
+ }
10360
+ if (methodName !== "parse" && methodName !== "stringify") {
10361
+ return yield* fail3("method is not parse or stringify");
10362
+ }
10363
+ return { node, methodName };
10364
+ });
10365
+ const effectTrySimple = (node) => gen(function* () {
10366
+ if (!ts.isCallExpression(node)) return yield* fail3("node is not a call expression");
10367
+ yield* typeParser.isNodeReferenceToEffectModuleApi("try")(node.expression);
10368
+ if (node.arguments.length === 0) return yield* fail3("Effect.try has no arguments");
10369
+ const lazyFn = yield* typeParser.lazyExpression(node.arguments[0]);
10370
+ const jsonMethod = yield* parseJsonMethod(lazyFn.expression);
10371
+ return { node: jsonMethod.node, methodName: jsonMethod.methodName };
10372
+ });
10373
+ const effectTryObject = (node) => gen(function* () {
10374
+ if (!ts.isCallExpression(node)) return yield* fail3("node is not a call expression");
10375
+ yield* typeParser.isNodeReferenceToEffectModuleApi("try")(node.expression);
10376
+ if (node.arguments.length === 0) return yield* fail3("Effect.try has no arguments");
10377
+ const arg = node.arguments[0];
10378
+ if (!ts.isObjectLiteralExpression(arg)) return yield* fail3("argument is not an object literal");
10379
+ const tryProp = arg.properties.find(
10380
+ (p) => ts.isPropertyAssignment(p) && ts.isIdentifier(p.name) && ts.idText(p.name) === "try"
10381
+ );
10382
+ if (!tryProp) return yield* fail3("object has no 'try' property");
10383
+ const lazyFn = yield* typeParser.lazyExpression(tryProp.initializer);
10384
+ const jsonMethod = yield* parseJsonMethod(lazyFn.expression);
10385
+ return { node: jsonMethod.node, methodName: jsonMethod.methodName };
10386
+ });
10387
+ const jsonMethodInEffectGen = (node) => gen(function* () {
10388
+ const jsonMethod = yield* parseJsonMethod(node);
10389
+ const { effectGen, scopeNode } = yield* typeParser.findEnclosingScopes(node);
10390
+ if (!effectGen || effectGen.body.statements.length === 0) {
10391
+ return yield* fail3("not inside an Effect generator");
10392
+ }
10393
+ if (scopeNode && scopeNode !== effectGen.generatorFunction) {
10394
+ return yield* fail3("inside a nested function scope");
10395
+ }
10396
+ return { node: jsonMethod.node, methodName: jsonMethod.methodName };
10397
+ });
10398
+ const nodeToVisit = [];
10399
+ const appendNodeToVisit = (node) => {
10400
+ nodeToVisit.push(node);
10401
+ return void 0;
10402
+ };
10403
+ ts.forEachChild(sourceFile, appendNodeToVisit);
10404
+ while (nodeToVisit.length > 0) {
10405
+ const node = nodeToVisit.shift();
10406
+ ts.forEachChild(node, appendNodeToVisit);
10407
+ const match3 = yield* pipe(
10408
+ firstSuccessOf([
10409
+ effectTrySimple(node),
10410
+ effectTryObject(node),
10411
+ jsonMethodInEffectGen(node)
10412
+ ]),
10413
+ option
10414
+ );
10415
+ if (isSome2(match3)) {
10416
+ report({
10417
+ location: match3.value.node,
10418
+ messageText: "Consider using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify",
10419
+ fixes: []
10420
+ });
10421
+ }
10422
+ }
10423
+ })
10424
+ });
10425
+
10077
10426
  // src/diagnostics/redundantSchemaTagIdentifier.ts
10078
10427
  var redundantSchemaTagIdentifier = createDiagnostic({
10079
10428
  name: "redundantSchemaTagIdentifier",
@@ -10226,108 +10575,91 @@ var runEffectInsideEffect = createDiagnostic({
10226
10575
  option
10227
10576
  );
10228
10577
  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
10578
+ const { effectGen, scopeNode } = yield* typeParser.findEnclosingScopes(node);
10579
+ if (effectGen && effectGen.body.statements.length > 0) {
10580
+ const nodeText = sourceFile.text.substring(
10581
+ ts.getTokenPosOfNode(node.expression, sourceFile),
10582
+ node.expression.end
10244
10583
  );
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
- }
10584
+ if (scopeNode && scopeNode !== effectGen.generatorFunction) {
10585
+ const fixAddRuntime = gen(function* () {
10586
+ const changeTracker = yield* service(ChangeTracker);
10587
+ const runtimeModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Runtime") || "Runtime";
10588
+ const effectModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect") || "Effect";
10589
+ let runtimeIdentifier = void 0;
10590
+ for (const statement of effectGen.generatorFunction.body.statements) {
10591
+ if (ts.isVariableStatement(statement) && statement.declarationList.declarations.length === 1) {
10592
+ const declaration = statement.declarationList.declarations[0];
10593
+ if (declaration.initializer && ts.isYieldExpression(declaration.initializer) && declaration.initializer.asteriskToken && declaration.initializer.expression) {
10594
+ const yieldedExpression = declaration.initializer.expression;
10595
+ if (ts.isCallExpression(yieldedExpression)) {
10596
+ const maybeEffectRuntime = yield* pipe(
10597
+ typeParser.isNodeReferenceToEffectModuleApi("runtime")(yieldedExpression.expression),
10598
+ option
10599
+ );
10600
+ if (isSome2(maybeEffectRuntime) && ts.isIdentifier(declaration.name)) {
10601
+ runtimeIdentifier = ts.idText(declaration.name);
10269
10602
  }
10270
10603
  }
10271
10604
  }
10272
10605
  }
10273
- if (!runtimeIdentifier) {
10274
- changeTracker.insertNodeAt(
10275
- sourceFile,
10276
- isInEffectGen.value.body.statements[0].pos,
10277
- ts.factory.createVariableStatement(
10606
+ }
10607
+ if (!runtimeIdentifier) {
10608
+ changeTracker.insertNodeAt(
10609
+ sourceFile,
10610
+ effectGen.body.statements[0].pos,
10611
+ ts.factory.createVariableStatement(
10612
+ void 0,
10613
+ ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(
10614
+ "effectRuntime",
10278
10615
  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
- )
10616
+ void 0,
10617
+ ts.factory.createYieldExpression(
10618
+ ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
10619
+ ts.factory.createCallExpression(
10620
+ ts.factory.createPropertyAccessExpression(
10621
+ ts.factory.createIdentifier(effectModuleIdentifier),
10622
+ "runtime"
10623
+ ),
10624
+ [ts.factory.createTypeReferenceNode("never")],
10625
+ []
10293
10626
  )
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"}, `
10627
+ )
10628
+ )], ts.NodeFlags.Const)
10629
+ ),
10630
+ {
10631
+ prefix: "\n",
10632
+ suffix: "\n"
10633
+ }
10310
10634
  );
10635
+ }
10636
+ changeTracker.deleteRange(sourceFile, {
10637
+ pos: ts.getTokenPosOfNode(node.expression, sourceFile),
10638
+ end: node.arguments[0].pos
10311
10639
  });
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.
10640
+ changeTracker.insertText(
10641
+ sourceFile,
10642
+ node.arguments[0].pos,
10643
+ `${runtimeModuleIdentifier}.${isEffectRunCall.value.methodName}(${runtimeIdentifier || "effectRuntime"}, `
10644
+ );
10645
+ });
10646
+ report({
10647
+ location: node.expression,
10648
+ messageText: `Using ${nodeText} inside an Effect is not recommended. The same runtime should generally be used instead to run child effects.
10315
10649
  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
- }
10650
+ fixes: [{
10651
+ fixName: "runEffectInsideEffect_fix",
10652
+ description: "Use a runtime to run the Effect",
10653
+ apply: fixAddRuntime
10654
+ }]
10655
+ });
10656
+ } else {
10657
+ report({
10658
+ location: node.expression,
10659
+ messageText: `Using ${nodeText} inside an Effect is not recommended. Effects inside generators can usually just be yielded.`,
10660
+ fixes: []
10661
+ });
10329
10662
  }
10330
- currentParent = currentParent.parent;
10331
10663
  }
10332
10664
  }
10333
10665
  })
@@ -10413,6 +10745,59 @@ var schemaStructWithTag = createDiagnostic({
10413
10745
  })
10414
10746
  });
10415
10747
 
10748
+ // src/diagnostics/schemaSyncInEffect.ts
10749
+ var syncToEffectMethod = {
10750
+ decodeSync: "decode",
10751
+ decodeUnknownSync: "decodeUnknown",
10752
+ encodeSync: "encode",
10753
+ encodeUnknownSync: "encodeUnknown"
10754
+ };
10755
+ var schemaSyncInEffect = createDiagnostic({
10756
+ name: "schemaSyncInEffect",
10757
+ code: 43,
10758
+ description: "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
10759
+ severity: "suggestion",
10760
+ apply: fn("schemaSyncInEffect.apply")(function* (sourceFile, report) {
10761
+ const ts = yield* service(TypeScriptApi);
10762
+ const typeParser = yield* service(TypeParser);
10763
+ const parseSchemaSyncMethod = (node, methodName) => pipe(
10764
+ typeParser.isNodeReferenceToEffectParseResultModuleApi(methodName)(node),
10765
+ map8(() => ({ node, methodName }))
10766
+ );
10767
+ const nodeToVisit = [];
10768
+ const appendNodeToVisit = (node) => {
10769
+ nodeToVisit.push(node);
10770
+ return void 0;
10771
+ };
10772
+ ts.forEachChild(sourceFile, appendNodeToVisit);
10773
+ while (nodeToVisit.length > 0) {
10774
+ const node = nodeToVisit.shift();
10775
+ ts.forEachChild(node, appendNodeToVisit);
10776
+ if (!ts.isCallExpression(node)) continue;
10777
+ const isSchemaSyncCall = yield* pipe(
10778
+ firstSuccessOf(
10779
+ Object.keys(syncToEffectMethod).map((methodName) => parseSchemaSyncMethod(node.expression, methodName))
10780
+ ),
10781
+ option
10782
+ );
10783
+ if (isNone2(isSchemaSyncCall)) continue;
10784
+ const { effectGen, scopeNode } = yield* typeParser.findEnclosingScopes(node);
10785
+ if (!effectGen || effectGen.body.statements.length === 0) continue;
10786
+ if (scopeNode && scopeNode !== effectGen.generatorFunction) continue;
10787
+ const nodeText = sourceFile.text.substring(
10788
+ ts.getTokenPosOfNode(node.expression, sourceFile),
10789
+ node.expression.end
10790
+ );
10791
+ const effectMethodName = syncToEffectMethod[isSchemaSyncCall.value.methodName];
10792
+ report({
10793
+ location: node.expression,
10794
+ messageText: `Using ${nodeText} inside an Effect generator is not recommended. Use Schema.${effectMethodName} instead to get properly typed ParseError in the error channel.`,
10795
+ fixes: []
10796
+ });
10797
+ }
10798
+ })
10799
+ });
10800
+
10416
10801
  // src/diagnostics/schemaUnionOfLiterals.ts
10417
10802
  var schemaUnionOfLiterals = createDiagnostic({
10418
10803
  name: "schemaUnionOfLiterals",
@@ -11127,7 +11512,9 @@ var diagnostics = [
11127
11512
  layerMergeAllWithDependencies,
11128
11513
  effectMapVoid,
11129
11514
  effectFnOpportunity,
11130
- redundantSchemaTagIdentifier
11515
+ redundantSchemaTagIdentifier,
11516
+ schemaSyncInEffect,
11517
+ preferSchemaOverJson
11131
11518
  ];
11132
11519
 
11133
11520
  // src/completions/effectDiagnosticsComment.ts