@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/cli.js CHANGED
@@ -30211,7 +30211,7 @@ var runMain3 = runMain2;
30211
30211
  // package.json
30212
30212
  var package_default = {
30213
30213
  name: "@effect/language-service",
30214
- version: "0.65.0",
30214
+ version: "0.66.1",
30215
30215
  packageManager: "pnpm@8.11.0",
30216
30216
  publishConfig: {
30217
30217
  access: "public",
@@ -33315,6 +33315,97 @@ function make64(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
33315
33315
  "TypeParser.effectFnGen",
33316
33316
  (node) => node
33317
33317
  );
33318
+ const findEnclosingScopes = fn2("TypeParser.findEnclosingScopes")(function* (startNode) {
33319
+ let currentParent = startNode.parent;
33320
+ let scopeNode = void 0;
33321
+ let effectGenResult = void 0;
33322
+ while (currentParent) {
33323
+ const nodeToCheck = currentParent;
33324
+ if (!scopeNode) {
33325
+ if (ts.isFunctionExpression(nodeToCheck) || ts.isFunctionDeclaration(nodeToCheck) || ts.isMethodDeclaration(nodeToCheck) || ts.isArrowFunction(nodeToCheck) || ts.isGetAccessorDeclaration(nodeToCheck) || ts.isSetAccessorDeclaration(nodeToCheck)) {
33326
+ scopeNode = nodeToCheck;
33327
+ }
33328
+ }
33329
+ if (!effectGenResult) {
33330
+ const isEffectGen = yield* pipe(
33331
+ effectGen(nodeToCheck),
33332
+ map34((result) => ({
33333
+ node: result.node,
33334
+ effectModule: result.effectModule,
33335
+ generatorFunction: result.generatorFunction,
33336
+ body: result.body
33337
+ })),
33338
+ orElse15(
33339
+ () => pipe(
33340
+ effectFnUntracedGen(nodeToCheck),
33341
+ map34((result) => ({
33342
+ node: result.node,
33343
+ effectModule: result.effectModule,
33344
+ generatorFunction: result.generatorFunction,
33345
+ body: result.body,
33346
+ pipeArguments: result.pipeArguments
33347
+ }))
33348
+ )
33349
+ ),
33350
+ orElse15(
33351
+ () => pipe(
33352
+ effectFnGen(nodeToCheck),
33353
+ map34((result) => ({
33354
+ node: result.node,
33355
+ effectModule: result.effectModule,
33356
+ generatorFunction: result.generatorFunction,
33357
+ body: result.body,
33358
+ pipeArguments: result.pipeArguments
33359
+ }))
33360
+ )
33361
+ ),
33362
+ option5
33363
+ );
33364
+ if (isSome2(isEffectGen)) {
33365
+ effectGenResult = isEffectGen.value;
33366
+ }
33367
+ }
33368
+ if (scopeNode && effectGenResult) {
33369
+ break;
33370
+ }
33371
+ currentParent = nodeToCheck.parent;
33372
+ }
33373
+ return { scopeNode, effectGen: effectGenResult };
33374
+ });
33375
+ const effectFn = cachedBy(
33376
+ function(node) {
33377
+ if (!ts.isCallExpression(node)) {
33378
+ return typeParserIssue("Node is not a call expression", void 0, node);
33379
+ }
33380
+ if (node.arguments.length === 0) {
33381
+ return typeParserIssue("Node has no arguments", void 0, node);
33382
+ }
33383
+ const regularFunction = node.arguments[0];
33384
+ if (!ts.isFunctionExpression(regularFunction) && !ts.isArrowFunction(regularFunction)) {
33385
+ return typeParserIssue("Node is not a function expression or arrow function", void 0, node);
33386
+ }
33387
+ if (ts.isFunctionExpression(regularFunction) && regularFunction.asteriskToken !== void 0) {
33388
+ return typeParserIssue("Node is a generator function, not a regular function", void 0, node);
33389
+ }
33390
+ const expressionToTest = ts.isCallExpression(node.expression) ? node.expression.expression : node.expression;
33391
+ if (!ts.isPropertyAccessExpression(expressionToTest)) {
33392
+ return typeParserIssue("Node is not a property access expression", void 0, node);
33393
+ }
33394
+ const propertyAccess = expressionToTest;
33395
+ const pipeArguments2 = node.arguments.slice(1);
33396
+ return pipe(
33397
+ isNodeReferenceToEffectModuleApi("fn")(propertyAccess),
33398
+ map34(() => ({
33399
+ node,
33400
+ effectModule: propertyAccess.expression,
33401
+ regularFunction,
33402
+ pipeArguments: pipeArguments2
33403
+ }))
33404
+ );
33405
+ },
33406
+ "TypeParser.effectFn",
33407
+ (node) => node
33408
+ );
33318
33409
  const unnecessaryEffectGen2 = cachedBy(
33319
33410
  fn2("TypeParser.unnecessaryEffectGen")(function* (node) {
33320
33411
  const { body } = yield* effectGen(node);
@@ -33426,6 +33517,28 @@ function make64(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
33426
33517
  `TypeParser.isNodeReferenceToEffectSchemaModuleApi(${memberName})`,
33427
33518
  (node) => node
33428
33519
  );
33520
+ const isEffectParseResultSourceFile = cachedBy(
33521
+ fn2("TypeParser.isEffectParseResultSourceFile")(function* (sourceFile) {
33522
+ const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
33523
+ if (!moduleSymbol) return yield* typeParserIssue("Node has no symbol", void 0, sourceFile);
33524
+ const parseIssueSymbol = typeChecker.tryGetMemberInModuleExports("ParseIssue", moduleSymbol);
33525
+ if (!parseIssueSymbol) return yield* typeParserIssue("ParseIssue type not found", void 0, sourceFile);
33526
+ const decodeSyncSymbol = typeChecker.tryGetMemberInModuleExports("decodeSync", moduleSymbol);
33527
+ if (!decodeSyncSymbol) return yield* typeParserIssue("decodeSync not found", void 0, sourceFile);
33528
+ const encodeSyncSymbol = typeChecker.tryGetMemberInModuleExports("encodeSync", moduleSymbol);
33529
+ if (!encodeSyncSymbol) return yield* typeParserIssue("encodeSync not found", void 0, sourceFile);
33530
+ return sourceFile;
33531
+ }),
33532
+ "TypeParser.isEffectParseResultSourceFile",
33533
+ (sourceFile) => sourceFile
33534
+ );
33535
+ const isNodeReferenceToEffectParseResultModuleApi = (memberName) => cachedBy(
33536
+ fn2("TypeParser.isNodeReferenceToEffectParseResultModuleApi")(function* (node) {
33537
+ return yield* isNodeReferenceToExportOfPackageModule(node, "effect", isEffectParseResultSourceFile, memberName);
33538
+ }),
33539
+ `TypeParser.isNodeReferenceToEffectParseResultModuleApi(${memberName})`,
33540
+ (node) => node
33541
+ );
33429
33542
  const contextTagVarianceStruct = (type2, atLocation) => map34(
33430
33543
  all9(
33431
33544
  varianceStructInvariantType(type2, atLocation, "_Identifier"),
@@ -34206,11 +34319,65 @@ function make64(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
34206
34319
  if (includeEffectFn) {
34207
34320
  const effectFnGenParsed = yield* pipe(effectFnGen(node), option5);
34208
34321
  const effectFnUntracedGenParsed = isNone2(effectFnGenParsed) ? yield* pipe(effectFnUntracedGen(node), option5) : none2();
34209
- const isEffectFn = isSome2(effectFnGenParsed);
34210
- const effectFnParsed = isEffectFn ? effectFnGenParsed : effectFnUntracedGenParsed;
34211
- const transformationKind = isEffectFn ? "effectFn" : "effectFnUntraced";
34212
- if (isSome2(effectFnParsed) && effectFnParsed.value.pipeArguments.length > 0) {
34213
- const fnResult = effectFnParsed.value;
34322
+ const effectFnNonGenParsed = isNone2(effectFnGenParsed) && isNone2(effectFnUntracedGenParsed) ? yield* pipe(effectFn(node), option5) : none2();
34323
+ const isEffectFnGen = isSome2(effectFnGenParsed);
34324
+ const isEffectFnUntracedGen = isSome2(effectFnUntracedGenParsed);
34325
+ const isEffectFnNonGen = isSome2(effectFnNonGenParsed);
34326
+ const transformationKind = isEffectFnUntracedGen ? "effectFnUntraced" : "effectFn";
34327
+ if (isEffectFnGen || isEffectFnUntracedGen) {
34328
+ const effectFnParsed = isEffectFnGen ? effectFnGenParsed : effectFnUntracedGenParsed;
34329
+ if (isSome2(effectFnParsed) && effectFnParsed.value.pipeArguments.length > 0) {
34330
+ const fnResult = effectFnParsed.value;
34331
+ const pipeArgs = fnResult.pipeArguments;
34332
+ const transformations = [];
34333
+ let subjectType;
34334
+ for (let i = 0; i < pipeArgs.length; i++) {
34335
+ const arg = pipeArgs[i];
34336
+ const contextualType = typeChecker.getContextualType(arg);
34337
+ const callSigs = contextualType ? typeChecker.getSignaturesOfType(contextualType, ts.SignatureKind.Call) : [];
34338
+ const outType = callSigs.length > 0 ? typeChecker.getReturnTypeOfSignature(callSigs[0]) : void 0;
34339
+ if (i === 0 && callSigs.length > 0) {
34340
+ const params = callSigs[0].parameters;
34341
+ if (params.length > 0) {
34342
+ subjectType = typeChecker.getTypeOfSymbol(params[0]);
34343
+ }
34344
+ }
34345
+ if (ts.isCallExpression(arg)) {
34346
+ transformations.push({
34347
+ callee: arg.expression,
34348
+ args: Array.from(arg.arguments),
34349
+ outType,
34350
+ kind: transformationKind
34351
+ });
34352
+ } else {
34353
+ transformations.push({
34354
+ callee: arg,
34355
+ args: void 0,
34356
+ outType,
34357
+ kind: transformationKind
34358
+ });
34359
+ }
34360
+ }
34361
+ const newFlow = {
34362
+ node,
34363
+ subject: {
34364
+ node,
34365
+ outType: subjectType
34366
+ },
34367
+ transformations
34368
+ };
34369
+ result.push(newFlow);
34370
+ workQueue.push([fnResult.body, void 0]);
34371
+ for (const arg of pipeArgs) {
34372
+ ts.forEachChild(arg, (c) => {
34373
+ workQueue.push([c, void 0]);
34374
+ });
34375
+ }
34376
+ continue;
34377
+ }
34378
+ }
34379
+ if (isEffectFnNonGen && isSome2(effectFnNonGenParsed) && effectFnNonGenParsed.value.pipeArguments.length > 0) {
34380
+ const fnResult = effectFnNonGenParsed.value;
34214
34381
  const pipeArgs = fnResult.pipeArguments;
34215
34382
  const transformations = [];
34216
34383
  let subjectType;
@@ -34230,14 +34397,14 @@ function make64(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
34230
34397
  callee: arg.expression,
34231
34398
  args: Array.from(arg.arguments),
34232
34399
  outType,
34233
- kind: transformationKind
34400
+ kind: "effectFn"
34234
34401
  });
34235
34402
  } else {
34236
34403
  transformations.push({
34237
34404
  callee: arg,
34238
34405
  args: void 0,
34239
34406
  outType,
34240
- kind: transformationKind
34407
+ kind: "effectFn"
34241
34408
  });
34242
34409
  }
34243
34410
  }
@@ -34250,7 +34417,16 @@ function make64(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
34250
34417
  transformations
34251
34418
  };
34252
34419
  result.push(newFlow);
34253
- workQueue.push([fnResult.body, void 0]);
34420
+ const regularFn = fnResult.regularFunction;
34421
+ if (ts.isArrowFunction(regularFn)) {
34422
+ if (ts.isBlock(regularFn.body)) {
34423
+ workQueue.push([regularFn.body, void 0]);
34424
+ } else {
34425
+ workQueue.push([regularFn.body, void 0]);
34426
+ }
34427
+ } else if (regularFn.body) {
34428
+ workQueue.push([regularFn.body, void 0]);
34429
+ }
34254
34430
  for (const arg of pipeArgs) {
34255
34431
  ts.forEachChild(arg, (c) => {
34256
34432
  workQueue.push([c, void 0]);
@@ -34313,6 +34489,7 @@ function make64(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
34313
34489
  return {
34314
34490
  isNodeReferenceToEffectModuleApi,
34315
34491
  isNodeReferenceToEffectSchemaModuleApi,
34492
+ isNodeReferenceToEffectParseResultModuleApi,
34316
34493
  isNodeReferenceToEffectDataModuleApi,
34317
34494
  isNodeReferenceToEffectContextModuleApi,
34318
34495
  isNodeReferenceToEffectSqlModelModuleApi,
@@ -34326,6 +34503,8 @@ function make64(ts, tsUtils, typeChecker, typeCheckerUtils, program) {
34326
34503
  effectGen,
34327
34504
  effectFnUntracedGen,
34328
34505
  effectFnGen,
34506
+ findEnclosingScopes,
34507
+ effectFn,
34329
34508
  extendsCauseYieldableError,
34330
34509
  unnecessaryEffectGen: unnecessaryEffectGen2,
34331
34510
  effectSchemaType,
@@ -36085,106 +36264,184 @@ ${versions.map((version) => `- found ${version} at ${resolvedPackages[packageNam
36085
36264
  });
36086
36265
 
36087
36266
  // src/diagnostics/effectFnOpportunity.ts
36088
- var parseEffectFnOpportunityTarget = (node, sourceFile) => gen3(function* () {
36089
- const ts = yield* service2(TypeScriptApi);
36090
- const typeChecker = yield* service2(TypeCheckerApi);
36091
- const typeParser = yield* service2(TypeParser);
36092
- const tsUtils = yield* service2(TypeScriptUtils);
36093
- if (!ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isFunctionDeclaration(node)) {
36094
- return yield* TypeParserIssue.issue;
36095
- }
36096
- if ((ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) && node.asteriskToken) {
36097
- return yield* TypeParserIssue.issue;
36098
- }
36099
- if (ts.isFunctionExpression(node) && node.name) {
36100
- return yield* TypeParserIssue.issue;
36101
- }
36102
- let bodyExpression;
36103
- if (ts.isArrowFunction(node)) {
36104
- if (ts.isBlock(node.body)) {
36105
- const returnStatement = findSingleReturnStatement(ts, node.body);
36106
- if (returnStatement?.expression) {
36107
- bodyExpression = returnStatement.expression;
36108
- }
36109
- } else {
36110
- bodyExpression = node.body;
36111
- }
36112
- } else if ((ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) && node.body) {
36113
- const returnStatement = findSingleReturnStatement(ts, node.body);
36114
- if (returnStatement?.expression) {
36115
- bodyExpression = returnStatement.expression;
36116
- }
36117
- }
36118
- if (!bodyExpression) return yield* TypeParserIssue.issue;
36119
- const { pipeArguments: pipeArguments2, subject } = yield* pipe(
36120
- typeParser.pipeCall(bodyExpression),
36121
- map34(({ args: args3, subject: subject2 }) => ({ subject: subject2, pipeArguments: args3 })),
36122
- orElse15(() => succeed17({ subject: bodyExpression, pipeArguments: [] }))
36123
- );
36124
- const { effectModule, generatorFunction } = yield* typeParser.effectGen(subject);
36125
- const functionType = typeChecker.getTypeAtLocation(node);
36126
- if (!functionType) return yield* TypeParserIssue.issue;
36127
- const callSignatures = typeChecker.getSignaturesOfType(functionType, ts.SignatureKind.Call);
36128
- if (callSignatures.length !== 1) return yield* TypeParserIssue.issue;
36129
- const signature = callSignatures[0];
36130
- const returnType = typeChecker.getReturnTypeOfSignature(signature);
36131
- const { A, E, R } = yield* typeParser.strictEffectType(returnType, node);
36132
- const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
36133
- sourceFile,
36134
- "effect",
36135
- "Effect"
36136
- ) || "Effect";
36137
- const nameIdentifier = getNameIdentifier(ts, node);
36138
- const traceName = nameIdentifier ? ts.isIdentifier(nameIdentifier) ? ts.idText(nameIdentifier) : nameIdentifier.text : void 0;
36139
- const hasReturnTypeAnnotation = !!node.type;
36140
- return {
36141
- node,
36142
- nameIdentifier,
36143
- effectModule,
36144
- generatorFunction,
36145
- effectModuleName,
36146
- traceName,
36147
- hasReturnTypeAnnotation,
36148
- effectTypes: { A, E, R },
36149
- pipeArguments: pipeArguments2
36150
- };
36151
- });
36152
36267
  var effectFnOpportunity = createDiagnostic({
36153
36268
  name: "effectFnOpportunity",
36154
36269
  code: 41,
36155
- description: "Suggests using Effect.fn for functions that return Effect.gen",
36270
+ description: "Suggests using Effect.fn for functions that returns an Effect",
36156
36271
  severity: "suggestion",
36157
36272
  apply: fn2("effectFnOpportunity.apply")(function* (sourceFile, report) {
36158
36273
  const ts = yield* service2(TypeScriptApi);
36159
36274
  const typeChecker = yield* service2(TypeCheckerApi);
36275
+ const typeCheckerUtils = yield* service2(TypeCheckerUtils);
36276
+ const typeParser = yield* service2(TypeParser);
36160
36277
  const tsUtils = yield* service2(TypeScriptUtils);
36161
- const createReturnTypeAnnotation = (effectModuleName, effectTypes, enclosingNode) => {
36162
- const { A, E, R } = effectTypes;
36163
- const aTypeNode = typeChecker.typeToTypeNode(A, enclosingNode, ts.NodeBuilderFlags.NoTruncation);
36164
- const eTypeNode = typeChecker.typeToTypeNode(E, enclosingNode, ts.NodeBuilderFlags.NoTruncation);
36165
- const rTypeNode = typeChecker.typeToTypeNode(R, enclosingNode, ts.NodeBuilderFlags.NoTruncation);
36166
- if (!aTypeNode || !eTypeNode || !rTypeNode) return void 0;
36167
- return ts.factory.createTypeReferenceNode(
36168
- ts.factory.createQualifiedName(
36169
- ts.factory.createQualifiedName(
36170
- ts.factory.createIdentifier(effectModuleName),
36171
- "fn"
36172
- ),
36173
- "Return"
36174
- ),
36175
- [aTypeNode, eTypeNode, rTypeNode]
36278
+ const sourceEffectModuleName = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
36279
+ sourceFile,
36280
+ "effect",
36281
+ "Effect"
36282
+ ) || "Effect";
36283
+ const findSingleReturnStatement = (block) => {
36284
+ if (block.statements.length !== 1) return void 0;
36285
+ const statement = block.statements[0];
36286
+ if (!ts.isReturnStatement(statement)) return void 0;
36287
+ return statement;
36288
+ };
36289
+ const getBodyExpression = (fnNode) => {
36290
+ if (ts.isArrowFunction(fnNode)) {
36291
+ if (ts.isBlock(fnNode.body)) {
36292
+ return findSingleReturnStatement(fnNode.body)?.expression;
36293
+ }
36294
+ return fnNode.body;
36295
+ } else if ((ts.isFunctionExpression(fnNode) || ts.isFunctionDeclaration(fnNode)) && fnNode.body) {
36296
+ return findSingleReturnStatement(fnNode.body)?.expression;
36297
+ }
36298
+ return void 0;
36299
+ };
36300
+ const getNameIdentifier = (node) => {
36301
+ if (ts.isFunctionDeclaration(node) && node.name) {
36302
+ return node.name;
36303
+ }
36304
+ if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
36305
+ return node.parent.name;
36306
+ }
36307
+ if (node.parent && ts.isPropertyAssignment(node.parent)) {
36308
+ const name = node.parent.name;
36309
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
36310
+ return name;
36311
+ }
36312
+ }
36313
+ if (node.parent && ts.isPropertyDeclaration(node.parent)) {
36314
+ const name = node.parent.name;
36315
+ if (ts.isIdentifier(name)) {
36316
+ return name;
36317
+ }
36318
+ }
36319
+ return void 0;
36320
+ };
36321
+ const areParametersReferencedIn = (fnNode, nodes2) => {
36322
+ if (fnNode.parameters.length === 0 || nodes2.length === 0) return false;
36323
+ const firstParam = fnNode.parameters[0];
36324
+ const lastParam = fnNode.parameters[fnNode.parameters.length - 1];
36325
+ const paramsStart = firstParam.pos;
36326
+ const paramsEnd = lastParam.end;
36327
+ const isSymbolDeclaredInParams = (symbol3) => {
36328
+ const declarations = symbol3.declarations;
36329
+ if (!declarations) return false;
36330
+ return declarations.some((decl) => decl.pos >= paramsStart && decl.end <= paramsEnd);
36331
+ };
36332
+ const nodesToVisit = [...nodes2];
36333
+ while (nodesToVisit.length > 0) {
36334
+ const node = nodesToVisit.shift();
36335
+ if (ts.isIdentifier(node)) {
36336
+ const symbol3 = typeChecker.getSymbolAtLocation(node);
36337
+ if (symbol3 && isSymbolDeclaredInParams(symbol3)) {
36338
+ return true;
36339
+ }
36340
+ }
36341
+ if (ts.isShorthandPropertyAssignment(node)) {
36342
+ const valueSymbol = typeChecker.getShorthandAssignmentValueSymbol(node);
36343
+ if (valueSymbol && isSymbolDeclaredInParams(valueSymbol)) {
36344
+ return true;
36345
+ }
36346
+ }
36347
+ ts.forEachChild(node, (child) => {
36348
+ nodesToVisit.push(child);
36349
+ return void 0;
36350
+ });
36351
+ }
36352
+ return false;
36353
+ };
36354
+ const tryParseGenOpportunity = (fnNode) => gen3(function* () {
36355
+ const bodyExpression = getBodyExpression(fnNode);
36356
+ if (!bodyExpression) return yield* TypeParserIssue.issue;
36357
+ const { pipeArguments: pipeArguments2, subject } = yield* pipe(
36358
+ typeParser.pipeCall(bodyExpression),
36359
+ map34(({ args: args3, subject: subject2 }) => ({ subject: subject2, pipeArguments: args3 })),
36360
+ orElse15(() => succeed17({ subject: bodyExpression, pipeArguments: [] }))
36361
+ );
36362
+ const { effectModule, generatorFunction } = yield* typeParser.effectGen(subject);
36363
+ const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
36364
+ return { effectModuleName, generatorFunction, pipeArguments: pipeArguments2 };
36365
+ });
36366
+ const isInsideEffectFn = (fnNode) => {
36367
+ const parent = fnNode.parent;
36368
+ if (!parent || !ts.isCallExpression(parent)) {
36369
+ return succeed17(false);
36370
+ }
36371
+ if (parent.arguments[0] !== fnNode) {
36372
+ return succeed17(false);
36373
+ }
36374
+ return pipe(
36375
+ typeParser.effectFn(parent),
36376
+ orElse15(() => typeParser.effectFnGen(parent)),
36377
+ orElse15(() => typeParser.effectFnUntracedGen(parent)),
36378
+ map34(() => true),
36379
+ orElse15(() => succeed17(false))
36176
36380
  );
36177
36381
  };
36178
- const createEffectFnNode = (originalNode, generatorFunction, effectModuleName, traceName, effectTypes, pipeArguments2) => {
36179
- const returnTypeAnnotation = effectTypes ? createReturnTypeAnnotation(effectModuleName, effectTypes, originalNode) : void 0;
36180
- const newGeneratorFunction = ts.factory.createFunctionExpression(
36382
+ const parseEffectFnOpportunityTarget = (node) => gen3(function* () {
36383
+ if (!ts.isFunctionExpression(node) && !ts.isArrowFunction(node) && !ts.isFunctionDeclaration(node)) {
36384
+ return yield* TypeParserIssue.issue;
36385
+ }
36386
+ if ((ts.isFunctionExpression(node) || ts.isFunctionDeclaration(node)) && node.asteriskToken) {
36387
+ return yield* TypeParserIssue.issue;
36388
+ }
36389
+ if (ts.isFunctionExpression(node) && node.name) {
36390
+ return yield* TypeParserIssue.issue;
36391
+ }
36392
+ if (yield* isInsideEffectFn(node)) {
36393
+ return yield* TypeParserIssue.issue;
36394
+ }
36395
+ const functionType = typeChecker.getTypeAtLocation(node);
36396
+ if (!functionType) return yield* TypeParserIssue.issue;
36397
+ const callSignatures = typeChecker.getSignaturesOfType(functionType, ts.SignatureKind.Call);
36398
+ if (callSignatures.length !== 1) return yield* TypeParserIssue.issue;
36399
+ const signature = callSignatures[0];
36400
+ const returnType = typeChecker.getReturnTypeOfSignature(signature);
36401
+ const unionMembers = typeCheckerUtils.unrollUnionMembers(returnType);
36402
+ yield* all9(...unionMembers.map((member) => typeParser.strictEffectType(member, node)));
36403
+ const nameIdentifier = getNameIdentifier(node);
36404
+ const traceName = nameIdentifier ? ts.isIdentifier(nameIdentifier) ? ts.idText(nameIdentifier) : nameIdentifier.text : void 0;
36405
+ if (!traceName) return yield* TypeParserIssue.issue;
36406
+ const opportunity = yield* pipe(
36407
+ tryParseGenOpportunity(node),
36408
+ orElse15(
36409
+ () => succeed17({ effectModuleName: sourceEffectModuleName, pipeArguments: [], generatorFunction: void 0 })
36410
+ )
36411
+ );
36412
+ return {
36413
+ node,
36414
+ nameIdentifier,
36415
+ effectModuleName: opportunity.effectModuleName,
36416
+ traceName,
36417
+ pipeArguments: opportunity.pipeArguments,
36418
+ generatorFunction: opportunity.generatorFunction,
36419
+ hasParamsInPipeArgs: areParametersReferencedIn(node, opportunity.pipeArguments)
36420
+ };
36421
+ });
36422
+ const getFunctionBodyBlock = (node) => {
36423
+ if (ts.isArrowFunction(node)) {
36424
+ if (ts.isBlock(node.body)) {
36425
+ return node.body;
36426
+ }
36427
+ return ts.factory.createBlock([ts.factory.createReturnStatement(node.body)], true);
36428
+ }
36429
+ return node.body;
36430
+ };
36431
+ const isGeneratorFunction2 = (node) => {
36432
+ if (ts.isArrowFunction(node)) return false;
36433
+ return node.asteriskToken !== void 0;
36434
+ };
36435
+ const createEffectFnNode = (originalNode, innerFunction, effectModuleName, traceName, pipeArguments2) => {
36436
+ const isGenerator = isGeneratorFunction2(innerFunction);
36437
+ const newFunction = ts.factory.createFunctionExpression(
36181
36438
  void 0,
36182
- ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
36439
+ isGenerator ? ts.factory.createToken(ts.SyntaxKind.AsteriskToken) : void 0,
36183
36440
  void 0,
36184
36441
  originalNode.typeParameters,
36185
36442
  originalNode.parameters,
36186
- returnTypeAnnotation,
36187
- generatorFunction.body
36443
+ void 0,
36444
+ getFunctionBodyBlock(innerFunction)
36188
36445
  );
36189
36446
  let fnExpression = ts.factory.createPropertyAccessExpression(
36190
36447
  ts.factory.createIdentifier(effectModuleName),
@@ -36197,34 +36454,27 @@ var effectFnOpportunity = createDiagnostic({
36197
36454
  [ts.factory.createStringLiteral(traceName)]
36198
36455
  );
36199
36456
  }
36200
- const effectFnCall = ts.factory.createCallExpression(
36201
- fnExpression,
36202
- void 0,
36203
- [newGeneratorFunction, ...pipeArguments2]
36204
- );
36457
+ const effectFnCall = ts.factory.createCallExpression(fnExpression, void 0, [newFunction, ...pipeArguments2]);
36205
36458
  if (ts.isFunctionDeclaration(originalNode)) {
36206
36459
  return tsUtils.tryPreserveDeclarationSemantics(originalNode, effectFnCall, false);
36207
36460
  }
36208
36461
  return effectFnCall;
36209
36462
  };
36210
- const createEffectFnUntracedNode = (originalNode, generatorFunction, effectModuleName, effectTypes, pipeArguments2) => {
36211
- const returnTypeAnnotation = effectTypes ? createReturnTypeAnnotation(effectModuleName, effectTypes, originalNode) : void 0;
36212
- const newGeneratorFunction = ts.factory.createFunctionExpression(
36463
+ const createEffectFnUntracedNode = (originalNode, innerFunction, effectModuleName, pipeArguments2) => {
36464
+ const isGenerator = isGeneratorFunction2(innerFunction);
36465
+ const newFunction = ts.factory.createFunctionExpression(
36213
36466
  void 0,
36214
- ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
36467
+ isGenerator ? ts.factory.createToken(ts.SyntaxKind.AsteriskToken) : void 0,
36215
36468
  void 0,
36216
36469
  originalNode.typeParameters,
36217
36470
  originalNode.parameters,
36218
- returnTypeAnnotation,
36219
- generatorFunction.body
36471
+ void 0,
36472
+ getFunctionBodyBlock(innerFunction)
36220
36473
  );
36221
36474
  const effectFnCall = ts.factory.createCallExpression(
36222
- ts.factory.createPropertyAccessExpression(
36223
- ts.factory.createIdentifier(effectModuleName),
36224
- "fnUntraced"
36225
- ),
36475
+ ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(effectModuleName), "fnUntraced"),
36226
36476
  void 0,
36227
- [newGeneratorFunction, ...pipeArguments2]
36477
+ [newFunction, ...pipeArguments2]
36228
36478
  );
36229
36479
  if (ts.isFunctionDeclaration(originalNode)) {
36230
36480
  return tsUtils.tryPreserveDeclarationSemantics(originalNode, effectFnCall, false);
@@ -36240,88 +36490,41 @@ var effectFnOpportunity = createDiagnostic({
36240
36490
  while (nodeToVisit.length > 0) {
36241
36491
  const node = nodeToVisit.shift();
36242
36492
  ts.forEachChild(node, appendNodeToVisit);
36243
- const target = yield* pipe(
36244
- parseEffectFnOpportunityTarget(node, sourceFile),
36245
- option5
36246
- );
36493
+ const target = yield* pipe(parseEffectFnOpportunityTarget(node), option5);
36247
36494
  if (isNone2(target)) continue;
36248
- const {
36249
- effectModuleName,
36250
- effectTypes,
36251
- generatorFunction,
36252
- hasReturnTypeAnnotation,
36253
- nameIdentifier,
36254
- node: targetNode,
36255
- pipeArguments: pipeArguments2,
36256
- traceName
36257
- } = target.value;
36495
+ if (target.value.hasParamsInPipeArgs) continue;
36496
+ const { effectModuleName, nameIdentifier, node: targetNode, pipeArguments: pipeArguments2, traceName } = target.value;
36497
+ const innerFunction = target.value.generatorFunction ?? targetNode;
36258
36498
  const fixes = [];
36259
36499
  fixes.push({
36260
36500
  fixName: "effectFnOpportunity_toEffectFn",
36261
36501
  description: traceName ? `Convert to Effect.fn("${traceName}")` : "Convert to Effect.fn",
36262
36502
  apply: gen3(function* () {
36263
36503
  const changeTracker = yield* service2(ChangeTracker);
36264
- const newNode = createEffectFnNode(
36265
- targetNode,
36266
- generatorFunction,
36267
- effectModuleName,
36268
- traceName,
36269
- hasReturnTypeAnnotation ? effectTypes : void 0,
36270
- pipeArguments2
36271
- );
36272
- changeTracker.replaceNode(sourceFile, targetNode, newNode);
36273
- })
36274
- });
36275
- fixes.push({
36276
- fixName: "effectFnOpportunity_toEffectFnUntraced",
36277
- description: "Convert to Effect.fnUntraced",
36278
- apply: gen3(function* () {
36279
- const changeTracker = yield* service2(ChangeTracker);
36280
- const newNode = createEffectFnUntracedNode(
36281
- targetNode,
36282
- generatorFunction,
36283
- effectModuleName,
36284
- hasReturnTypeAnnotation ? effectTypes : void 0,
36285
- pipeArguments2
36286
- );
36504
+ const newNode = createEffectFnNode(targetNode, innerFunction, effectModuleName, traceName, pipeArguments2);
36287
36505
  changeTracker.replaceNode(sourceFile, targetNode, newNode);
36288
36506
  })
36289
36507
  });
36508
+ if (target.value.generatorFunction) {
36509
+ fixes.push({
36510
+ fixName: "effectFnOpportunity_toEffectFnUntraced",
36511
+ description: "Convert to Effect.fnUntraced",
36512
+ apply: gen3(function* () {
36513
+ const changeTracker = yield* service2(ChangeTracker);
36514
+ const newNode = createEffectFnUntracedNode(targetNode, innerFunction, effectModuleName, pipeArguments2);
36515
+ changeTracker.replaceNode(sourceFile, targetNode, newNode);
36516
+ })
36517
+ });
36518
+ }
36519
+ const pipeArgsSuffix = pipeArguments2.length > 0 ? ` Effect.fn also accepts the piped transformations as additional arguments.` : ``;
36290
36520
  report({
36291
36521
  location: nameIdentifier ?? targetNode,
36292
- messageText: `This function could benefit from Effect.fn's automatic tracing and concise syntax, or Effect.fnUntraced to get just a more concise syntax.`,
36522
+ 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}`,
36293
36523
  fixes
36294
36524
  });
36295
36525
  }
36296
36526
  })
36297
36527
  });
36298
- function findSingleReturnStatement(ts, block) {
36299
- if (block.statements.length !== 1) return void 0;
36300
- const statement = block.statements[0];
36301
- if (!ts.isReturnStatement(statement)) return void 0;
36302
- return statement;
36303
- }
36304
- function getNameIdentifier(ts, node) {
36305
- if (ts.isFunctionDeclaration(node) && node.name) {
36306
- return node.name;
36307
- }
36308
- if (node.parent && ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
36309
- return node.parent.name;
36310
- }
36311
- if (node.parent && ts.isPropertyAssignment(node.parent)) {
36312
- const name = node.parent.name;
36313
- if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
36314
- return name;
36315
- }
36316
- }
36317
- if (node.parent && ts.isPropertyDeclaration(node.parent)) {
36318
- const name = node.parent.name;
36319
- if (ts.isIdentifier(name)) {
36320
- return name;
36321
- }
36322
- }
36323
- return void 0;
36324
- }
36325
36528
 
36326
36529
  // src/diagnostics/effectGenUsesAdapter.ts
36327
36530
  var effectGenUsesAdapter = createDiagnostic({
@@ -37084,6 +37287,52 @@ var missedPipeableOpportunity = createDiagnostic({
37084
37287
  const typeChecker = yield* service2(TypeCheckerApi);
37085
37288
  const typeParser = yield* service2(TypeParser);
37086
37289
  const options3 = yield* service2(LanguageServicePluginOptions);
37290
+ const isSafelyPipeableCallee = (callee) => {
37291
+ if (ts.isCallExpression(callee)) {
37292
+ return true;
37293
+ }
37294
+ if (ts.isArrowFunction(callee)) {
37295
+ return true;
37296
+ }
37297
+ if (ts.isFunctionExpression(callee)) {
37298
+ return true;
37299
+ }
37300
+ if (ts.isParenthesizedExpression(callee)) {
37301
+ return isSafelyPipeableCallee(callee.expression);
37302
+ }
37303
+ if (ts.isIdentifier(callee)) {
37304
+ const symbol3 = typeChecker.getSymbolAtLocation(callee);
37305
+ if (!symbol3) return false;
37306
+ if (symbol3.flags & (ts.SymbolFlags.Module | ts.SymbolFlags.Namespace | ts.SymbolFlags.ValueModule)) {
37307
+ return true;
37308
+ }
37309
+ const declarations = symbol3.declarations;
37310
+ if (declarations && declarations.length > 0) {
37311
+ const decl = declarations[0];
37312
+ if (ts.isFunctionDeclaration(decl) || ts.isVariableDeclaration(decl) || ts.isImportSpecifier(decl) || ts.isImportClause(decl) || ts.isNamespaceImport(decl)) {
37313
+ return true;
37314
+ }
37315
+ }
37316
+ return false;
37317
+ }
37318
+ if (ts.isPropertyAccessExpression(callee)) {
37319
+ const subject = callee.expression;
37320
+ const symbol3 = typeChecker.getSymbolAtLocation(subject);
37321
+ if (!symbol3) return false;
37322
+ if (symbol3.flags & (ts.SymbolFlags.Module | ts.SymbolFlags.Namespace | ts.SymbolFlags.ValueModule)) {
37323
+ return true;
37324
+ }
37325
+ const declarations = symbol3.declarations;
37326
+ if (declarations && declarations.length > 0) {
37327
+ const decl = declarations[0];
37328
+ if (ts.isNamespaceImport(decl) || ts.isSourceFile(decl) || ts.isModuleDeclaration(decl)) {
37329
+ return true;
37330
+ }
37331
+ }
37332
+ return false;
37333
+ }
37334
+ return false;
37335
+ };
37087
37336
  const flows = yield* typeParser.pipingFlows(false)(sourceFile);
37088
37337
  for (const flow2 of flows) {
37089
37338
  if (flow2.transformations.length < options3.pipeableMinArgCount) {
@@ -37097,76 +37346,92 @@ var missedPipeableOpportunity = createDiagnostic({
37097
37346
  if (callSigs.length > 0) {
37098
37347
  continue;
37099
37348
  }
37100
- let firstPipeableIndex = -1;
37101
- const subjectType = flow2.subject.outType;
37102
- if (!subjectType) {
37103
- continue;
37104
- }
37105
- const subjectIsPipeable = yield* pipe(
37106
- typeParser.pipeableType(subjectType, flow2.subject.node),
37107
- option5
37108
- );
37109
- if (subjectIsPipeable._tag === "Some") {
37110
- firstPipeableIndex = 0;
37111
- } else {
37112
- for (let i = 0; i < flow2.transformations.length; i++) {
37113
- const t = flow2.transformations[i];
37114
- if (t.outType) {
37115
- const isPipeable = yield* pipe(
37116
- typeParser.pipeableType(t.outType, flow2.node),
37117
- option5
37118
- );
37119
- if (isPipeable._tag === "Some") {
37120
- firstPipeableIndex = i + 1;
37121
- break;
37122
- }
37349
+ const isPipeableAtIndex = function* (index) {
37350
+ if (index === 0) {
37351
+ const subjectType = flow2.subject.outType;
37352
+ if (!subjectType) return false;
37353
+ const result = yield* pipe(
37354
+ typeParser.pipeableType(subjectType, flow2.subject.node),
37355
+ option5
37356
+ );
37357
+ return result._tag === "Some";
37358
+ } else {
37359
+ const t = flow2.transformations[index - 1];
37360
+ if (!t.outType) return false;
37361
+ const result = yield* pipe(
37362
+ typeParser.pipeableType(t.outType, flow2.node),
37363
+ option5
37364
+ );
37365
+ return result._tag === "Some";
37366
+ }
37367
+ };
37368
+ let searchStartIndex = 0;
37369
+ while (searchStartIndex <= flow2.transformations.length) {
37370
+ let firstPipeableIndex = -1;
37371
+ for (let i = searchStartIndex; i <= flow2.transformations.length; i++) {
37372
+ if (yield* isPipeableAtIndex(i)) {
37373
+ firstPipeableIndex = i;
37374
+ break;
37123
37375
  }
37124
37376
  }
37125
- }
37126
- if (firstPipeableIndex === -1) {
37127
- continue;
37128
- }
37129
- const transformationsAfterPipeable = flow2.transformations.slice(firstPipeableIndex);
37130
- const callKindCount = transformationsAfterPipeable.filter((t) => t.kind === "call").length;
37131
- if (callKindCount < options3.pipeableMinArgCount) {
37132
- continue;
37133
- }
37134
- const pipeableSubjectNode = firstPipeableIndex === 0 ? flow2.subject.node : typeParser.reconstructPipingFlow({
37135
- subject: flow2.subject,
37136
- transformations: flow2.transformations.slice(0, firstPipeableIndex)
37137
- });
37138
- const pipeableTransformations = flow2.transformations.slice(firstPipeableIndex);
37139
- report({
37140
- location: flow2.node,
37141
- messageText: `Nested function calls can be converted to pipeable style for better readability.`,
37142
- fixes: [{
37143
- fixName: "missedPipeableOpportunity_fix",
37144
- description: "Convert to pipe style",
37145
- apply: gen3(function* () {
37146
- const changeTracker = yield* service2(ChangeTracker);
37147
- const pipeArgs = pipeableTransformations.map((t) => {
37148
- if (t.args) {
37149
- return ts.factory.createCallExpression(
37150
- t.callee,
37377
+ if (firstPipeableIndex === -1) {
37378
+ break;
37379
+ }
37380
+ const pipeableTransformations = [];
37381
+ for (let i = firstPipeableIndex; i < flow2.transformations.length; i++) {
37382
+ const t = flow2.transformations[i];
37383
+ if (!isSafelyPipeableCallee(t.callee)) {
37384
+ break;
37385
+ }
37386
+ pipeableTransformations.push(t);
37387
+ }
37388
+ const callKindCount = pipeableTransformations.filter((t) => t.kind === "call").length;
37389
+ if (callKindCount >= options3.pipeableMinArgCount) {
37390
+ const pipeableEndIndex = firstPipeableIndex + pipeableTransformations.length;
37391
+ const pipeableSubjectNode = firstPipeableIndex === 0 ? flow2.subject.node : typeParser.reconstructPipingFlow({
37392
+ subject: flow2.subject,
37393
+ transformations: flow2.transformations.slice(0, firstPipeableIndex)
37394
+ });
37395
+ const afterTransformations = flow2.transformations.slice(pipeableEndIndex);
37396
+ report({
37397
+ location: flow2.node,
37398
+ messageText: `Nested function calls can be converted to pipeable style for better readability.`,
37399
+ fixes: [{
37400
+ fixName: "missedPipeableOpportunity_fix",
37401
+ description: "Convert to pipe style",
37402
+ apply: gen3(function* () {
37403
+ const changeTracker = yield* service2(ChangeTracker);
37404
+ const pipeArgs = pipeableTransformations.map((t) => {
37405
+ if (t.args) {
37406
+ return ts.factory.createCallExpression(
37407
+ t.callee,
37408
+ void 0,
37409
+ t.args
37410
+ );
37411
+ } else {
37412
+ return t.callee;
37413
+ }
37414
+ });
37415
+ const pipeNode = ts.factory.createCallExpression(
37416
+ ts.factory.createPropertyAccessExpression(
37417
+ pipeableSubjectNode,
37418
+ "pipe"
37419
+ ),
37151
37420
  void 0,
37152
- t.args
37421
+ pipeArgs
37153
37422
  );
37154
- } else {
37155
- return t.callee;
37156
- }
37157
- });
37158
- const newNode = ts.factory.createCallExpression(
37159
- ts.factory.createPropertyAccessExpression(
37160
- pipeableSubjectNode,
37161
- "pipe"
37162
- ),
37163
- void 0,
37164
- pipeArgs
37165
- );
37166
- changeTracker.replaceNode(sourceFile, flow2.node, newNode);
37167
- })
37168
- }]
37169
- });
37423
+ const newNode = afterTransformations.length > 0 ? typeParser.reconstructPipingFlow({
37424
+ subject: { node: pipeNode, outType: void 0 },
37425
+ transformations: afterTransformations
37426
+ }) : pipeNode;
37427
+ changeTracker.replaceNode(sourceFile, flow2.node, newNode);
37428
+ })
37429
+ }]
37430
+ });
37431
+ break;
37432
+ }
37433
+ searchStartIndex = firstPipeableIndex + pipeableTransformations.length + 1;
37434
+ }
37170
37435
  }
37171
37436
  })
37172
37437
  });
@@ -38012,6 +38277,90 @@ var overriddenSchemaConstructor = createDiagnostic({
38012
38277
  })
38013
38278
  });
38014
38279
 
38280
+ // src/diagnostics/preferSchemaOverJson.ts
38281
+ var preferSchemaOverJson = createDiagnostic({
38282
+ name: "preferSchemaOverJson",
38283
+ code: 44,
38284
+ description: "Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify which may throw",
38285
+ severity: "suggestion",
38286
+ apply: fn2("preferSchemaOverJson.apply")(function* (sourceFile, report) {
38287
+ const ts = yield* service2(TypeScriptApi);
38288
+ const typeParser = yield* service2(TypeParser);
38289
+ const parseJsonMethod = (node) => gen3(function* () {
38290
+ if (!ts.isCallExpression(node)) return yield* fail18("node is not a call expression");
38291
+ const expression = node.expression;
38292
+ if (!ts.isPropertyAccessExpression(expression)) return yield* fail18("expression is not a property access");
38293
+ const objectExpr = expression.expression;
38294
+ const methodName = ts.idText(expression.name);
38295
+ if (!ts.isIdentifier(objectExpr) || ts.idText(objectExpr) !== "JSON") {
38296
+ return yield* fail18("object is not JSON");
38297
+ }
38298
+ if (methodName !== "parse" && methodName !== "stringify") {
38299
+ return yield* fail18("method is not parse or stringify");
38300
+ }
38301
+ return { node, methodName };
38302
+ });
38303
+ const effectTrySimple = (node) => gen3(function* () {
38304
+ if (!ts.isCallExpression(node)) return yield* fail18("node is not a call expression");
38305
+ yield* typeParser.isNodeReferenceToEffectModuleApi("try")(node.expression);
38306
+ if (node.arguments.length === 0) return yield* fail18("Effect.try has no arguments");
38307
+ const lazyFn = yield* typeParser.lazyExpression(node.arguments[0]);
38308
+ const jsonMethod = yield* parseJsonMethod(lazyFn.expression);
38309
+ return { node: jsonMethod.node, methodName: jsonMethod.methodName };
38310
+ });
38311
+ const effectTryObject = (node) => gen3(function* () {
38312
+ if (!ts.isCallExpression(node)) return yield* fail18("node is not a call expression");
38313
+ yield* typeParser.isNodeReferenceToEffectModuleApi("try")(node.expression);
38314
+ if (node.arguments.length === 0) return yield* fail18("Effect.try has no arguments");
38315
+ const arg = node.arguments[0];
38316
+ if (!ts.isObjectLiteralExpression(arg)) return yield* fail18("argument is not an object literal");
38317
+ const tryProp = arg.properties.find(
38318
+ (p3) => ts.isPropertyAssignment(p3) && ts.isIdentifier(p3.name) && ts.idText(p3.name) === "try"
38319
+ );
38320
+ if (!tryProp) return yield* fail18("object has no 'try' property");
38321
+ const lazyFn = yield* typeParser.lazyExpression(tryProp.initializer);
38322
+ const jsonMethod = yield* parseJsonMethod(lazyFn.expression);
38323
+ return { node: jsonMethod.node, methodName: jsonMethod.methodName };
38324
+ });
38325
+ const jsonMethodInEffectGen = (node) => gen3(function* () {
38326
+ const jsonMethod = yield* parseJsonMethod(node);
38327
+ const { effectGen, scopeNode } = yield* typeParser.findEnclosingScopes(node);
38328
+ if (!effectGen || effectGen.body.statements.length === 0) {
38329
+ return yield* fail18("not inside an Effect generator");
38330
+ }
38331
+ if (scopeNode && scopeNode !== effectGen.generatorFunction) {
38332
+ return yield* fail18("inside a nested function scope");
38333
+ }
38334
+ return { node: jsonMethod.node, methodName: jsonMethod.methodName };
38335
+ });
38336
+ const nodeToVisit = [];
38337
+ const appendNodeToVisit = (node) => {
38338
+ nodeToVisit.push(node);
38339
+ return void 0;
38340
+ };
38341
+ ts.forEachChild(sourceFile, appendNodeToVisit);
38342
+ while (nodeToVisit.length > 0) {
38343
+ const node = nodeToVisit.shift();
38344
+ ts.forEachChild(node, appendNodeToVisit);
38345
+ const match18 = yield* pipe(
38346
+ firstSuccessOf2([
38347
+ effectTrySimple(node),
38348
+ effectTryObject(node),
38349
+ jsonMethodInEffectGen(node)
38350
+ ]),
38351
+ option5
38352
+ );
38353
+ if (isSome2(match18)) {
38354
+ report({
38355
+ location: match18.value.node,
38356
+ messageText: "Consider using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify",
38357
+ fixes: []
38358
+ });
38359
+ }
38360
+ }
38361
+ })
38362
+ });
38363
+
38015
38364
  // src/diagnostics/redundantSchemaTagIdentifier.ts
38016
38365
  var redundantSchemaTagIdentifier = createDiagnostic({
38017
38366
  name: "redundantSchemaTagIdentifier",
@@ -38164,108 +38513,91 @@ var runEffectInsideEffect = createDiagnostic({
38164
38513
  option5
38165
38514
  );
38166
38515
  if (isNone2(isEffectRunCall)) continue;
38167
- let currentParent = node.parent;
38168
- let nodeIntroduceScope = void 0;
38169
- while (currentParent) {
38170
- const possiblyEffectGen = currentParent;
38171
- if (!nodeIntroduceScope) {
38172
- if (ts.isFunctionExpression(possiblyEffectGen) || ts.isFunctionDeclaration(possiblyEffectGen) || ts.isMethodDeclaration(possiblyEffectGen) || ts.isArrowFunction(possiblyEffectGen)) {
38173
- nodeIntroduceScope = possiblyEffectGen;
38174
- continue;
38175
- }
38176
- }
38177
- const isInEffectGen = yield* pipe(
38178
- typeParser.effectGen(possiblyEffectGen),
38179
- orElse15(() => typeParser.effectFnUntracedGen(possiblyEffectGen)),
38180
- orElse15(() => typeParser.effectFnGen(possiblyEffectGen)),
38181
- option5
38516
+ const { effectGen, scopeNode } = yield* typeParser.findEnclosingScopes(node);
38517
+ if (effectGen && effectGen.body.statements.length > 0) {
38518
+ const nodeText = sourceFile.text.substring(
38519
+ ts.getTokenPosOfNode(node.expression, sourceFile),
38520
+ node.expression.end
38182
38521
  );
38183
- if (isSome2(isInEffectGen) && isInEffectGen.value.body.statements.length > 0) {
38184
- const nodeText = sourceFile.text.substring(
38185
- ts.getTokenPosOfNode(node.expression, sourceFile),
38186
- node.expression.end
38187
- );
38188
- if (nodeIntroduceScope && nodeIntroduceScope !== isInEffectGen.value.generatorFunction) {
38189
- const fixAddRuntime = gen3(function* () {
38190
- const changeTracker = yield* service2(ChangeTracker);
38191
- const runtimeModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Runtime") || "Runtime";
38192
- const effectModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect") || "Effect";
38193
- let runtimeIdentifier = void 0;
38194
- for (const statement of isInEffectGen.value.generatorFunction.body.statements) {
38195
- if (ts.isVariableStatement(statement) && statement.declarationList.declarations.length === 1) {
38196
- const declaration = statement.declarationList.declarations[0];
38197
- if (declaration.initializer && ts.isYieldExpression(declaration.initializer) && declaration.initializer.asteriskToken && declaration.initializer.expression) {
38198
- const yieldedExpression = declaration.initializer.expression;
38199
- if (ts.isCallExpression(yieldedExpression)) {
38200
- const maybeEffectRuntime = yield* pipe(
38201
- typeParser.isNodeReferenceToEffectModuleApi("runtime")(yieldedExpression.expression),
38202
- option5
38203
- );
38204
- if (isSome2(maybeEffectRuntime) && ts.isIdentifier(declaration.name)) {
38205
- runtimeIdentifier = ts.idText(declaration.name);
38206
- }
38522
+ if (scopeNode && scopeNode !== effectGen.generatorFunction) {
38523
+ const fixAddRuntime = gen3(function* () {
38524
+ const changeTracker = yield* service2(ChangeTracker);
38525
+ const runtimeModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Runtime") || "Runtime";
38526
+ const effectModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect") || "Effect";
38527
+ let runtimeIdentifier = void 0;
38528
+ for (const statement of effectGen.generatorFunction.body.statements) {
38529
+ if (ts.isVariableStatement(statement) && statement.declarationList.declarations.length === 1) {
38530
+ const declaration = statement.declarationList.declarations[0];
38531
+ if (declaration.initializer && ts.isYieldExpression(declaration.initializer) && declaration.initializer.asteriskToken && declaration.initializer.expression) {
38532
+ const yieldedExpression = declaration.initializer.expression;
38533
+ if (ts.isCallExpression(yieldedExpression)) {
38534
+ const maybeEffectRuntime = yield* pipe(
38535
+ typeParser.isNodeReferenceToEffectModuleApi("runtime")(yieldedExpression.expression),
38536
+ option5
38537
+ );
38538
+ if (isSome2(maybeEffectRuntime) && ts.isIdentifier(declaration.name)) {
38539
+ runtimeIdentifier = ts.idText(declaration.name);
38207
38540
  }
38208
38541
  }
38209
38542
  }
38210
38543
  }
38211
- if (!runtimeIdentifier) {
38212
- changeTracker.insertNodeAt(
38213
- sourceFile,
38214
- isInEffectGen.value.body.statements[0].pos,
38215
- ts.factory.createVariableStatement(
38544
+ }
38545
+ if (!runtimeIdentifier) {
38546
+ changeTracker.insertNodeAt(
38547
+ sourceFile,
38548
+ effectGen.body.statements[0].pos,
38549
+ ts.factory.createVariableStatement(
38550
+ void 0,
38551
+ ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(
38552
+ "effectRuntime",
38216
38553
  void 0,
38217
- ts.factory.createVariableDeclarationList([ts.factory.createVariableDeclaration(
38218
- "effectRuntime",
38219
- void 0,
38220
- void 0,
38221
- ts.factory.createYieldExpression(
38222
- ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
38223
- ts.factory.createCallExpression(
38224
- ts.factory.createPropertyAccessExpression(
38225
- ts.factory.createIdentifier(effectModuleIdentifier),
38226
- "runtime"
38227
- ),
38228
- [ts.factory.createTypeReferenceNode("never")],
38229
- []
38230
- )
38554
+ void 0,
38555
+ ts.factory.createYieldExpression(
38556
+ ts.factory.createToken(ts.SyntaxKind.AsteriskToken),
38557
+ ts.factory.createCallExpression(
38558
+ ts.factory.createPropertyAccessExpression(
38559
+ ts.factory.createIdentifier(effectModuleIdentifier),
38560
+ "runtime"
38561
+ ),
38562
+ [ts.factory.createTypeReferenceNode("never")],
38563
+ []
38231
38564
  )
38232
- )], ts.NodeFlags.Const)
38233
- ),
38234
- {
38235
- prefix: "\n",
38236
- suffix: "\n"
38237
- }
38238
- );
38239
- }
38240
- changeTracker.deleteRange(sourceFile, {
38241
- pos: ts.getTokenPosOfNode(node.expression, sourceFile),
38242
- end: node.arguments[0].pos
38243
- });
38244
- changeTracker.insertText(
38245
- sourceFile,
38246
- node.arguments[0].pos,
38247
- `${runtimeModuleIdentifier}.${isEffectRunCall.value.methodName}(${runtimeIdentifier || "effectRuntime"}, `
38565
+ )
38566
+ )], ts.NodeFlags.Const)
38567
+ ),
38568
+ {
38569
+ prefix: "\n",
38570
+ suffix: "\n"
38571
+ }
38248
38572
  );
38573
+ }
38574
+ changeTracker.deleteRange(sourceFile, {
38575
+ pos: ts.getTokenPosOfNode(node.expression, sourceFile),
38576
+ end: node.arguments[0].pos
38249
38577
  });
38250
- report({
38251
- location: node.expression,
38252
- messageText: `Using ${nodeText} inside an Effect is not recommended. The same runtime should generally be used instead to run child effects.
38578
+ changeTracker.insertText(
38579
+ sourceFile,
38580
+ node.arguments[0].pos,
38581
+ `${runtimeModuleIdentifier}.${isEffectRunCall.value.methodName}(${runtimeIdentifier || "effectRuntime"}, `
38582
+ );
38583
+ });
38584
+ report({
38585
+ location: node.expression,
38586
+ messageText: `Using ${nodeText} inside an Effect is not recommended. The same runtime should generally be used instead to run child effects.
38253
38587
  Consider extracting the Runtime by using for example Effect.runtime and then use Runtime.${isEffectRunCall.value.methodName} with the extracted runtime instead.`,
38254
- fixes: [{
38255
- fixName: "runEffectInsideEffect_fix",
38256
- description: "Use a runtime to run the Effect",
38257
- apply: fixAddRuntime
38258
- }]
38259
- });
38260
- } else {
38261
- report({
38262
- location: node.expression,
38263
- messageText: `Using ${nodeText} inside an Effect is not recommended. Effects inside generators can usually just be yielded.`,
38264
- fixes: []
38265
- });
38266
- }
38588
+ fixes: [{
38589
+ fixName: "runEffectInsideEffect_fix",
38590
+ description: "Use a runtime to run the Effect",
38591
+ apply: fixAddRuntime
38592
+ }]
38593
+ });
38594
+ } else {
38595
+ report({
38596
+ location: node.expression,
38597
+ messageText: `Using ${nodeText} inside an Effect is not recommended. Effects inside generators can usually just be yielded.`,
38598
+ fixes: []
38599
+ });
38267
38600
  }
38268
- currentParent = currentParent.parent;
38269
38601
  }
38270
38602
  }
38271
38603
  })
@@ -38351,6 +38683,59 @@ var schemaStructWithTag = createDiagnostic({
38351
38683
  })
38352
38684
  });
38353
38685
 
38686
+ // src/diagnostics/schemaSyncInEffect.ts
38687
+ var syncToEffectMethod = {
38688
+ decodeSync: "decode",
38689
+ decodeUnknownSync: "decodeUnknown",
38690
+ encodeSync: "encode",
38691
+ encodeUnknownSync: "encodeUnknown"
38692
+ };
38693
+ var schemaSyncInEffect = createDiagnostic({
38694
+ name: "schemaSyncInEffect",
38695
+ code: 43,
38696
+ description: "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
38697
+ severity: "suggestion",
38698
+ apply: fn2("schemaSyncInEffect.apply")(function* (sourceFile, report) {
38699
+ const ts = yield* service2(TypeScriptApi);
38700
+ const typeParser = yield* service2(TypeParser);
38701
+ const parseSchemaSyncMethod = (node, methodName) => pipe(
38702
+ typeParser.isNodeReferenceToEffectParseResultModuleApi(methodName)(node),
38703
+ map34(() => ({ node, methodName }))
38704
+ );
38705
+ const nodeToVisit = [];
38706
+ const appendNodeToVisit = (node) => {
38707
+ nodeToVisit.push(node);
38708
+ return void 0;
38709
+ };
38710
+ ts.forEachChild(sourceFile, appendNodeToVisit);
38711
+ while (nodeToVisit.length > 0) {
38712
+ const node = nodeToVisit.shift();
38713
+ ts.forEachChild(node, appendNodeToVisit);
38714
+ if (!ts.isCallExpression(node)) continue;
38715
+ const isSchemaSyncCall = yield* pipe(
38716
+ firstSuccessOf2(
38717
+ Object.keys(syncToEffectMethod).map((methodName) => parseSchemaSyncMethod(node.expression, methodName))
38718
+ ),
38719
+ option5
38720
+ );
38721
+ if (isNone2(isSchemaSyncCall)) continue;
38722
+ const { effectGen, scopeNode } = yield* typeParser.findEnclosingScopes(node);
38723
+ if (!effectGen || effectGen.body.statements.length === 0) continue;
38724
+ if (scopeNode && scopeNode !== effectGen.generatorFunction) continue;
38725
+ const nodeText = sourceFile.text.substring(
38726
+ ts.getTokenPosOfNode(node.expression, sourceFile),
38727
+ node.expression.end
38728
+ );
38729
+ const effectMethodName = syncToEffectMethod[isSchemaSyncCall.value.methodName];
38730
+ report({
38731
+ location: node.expression,
38732
+ messageText: `Using ${nodeText} inside an Effect generator is not recommended. Use Schema.${effectMethodName} instead to get properly typed ParseError in the error channel.`,
38733
+ fixes: []
38734
+ });
38735
+ }
38736
+ })
38737
+ });
38738
+
38354
38739
  // src/diagnostics/schemaUnionOfLiterals.ts
38355
38740
  var schemaUnionOfLiterals = createDiagnostic({
38356
38741
  name: "schemaUnionOfLiterals",
@@ -39065,7 +39450,9 @@ var diagnostics = [
39065
39450
  layerMergeAllWithDependencies,
39066
39451
  effectMapVoid,
39067
39452
  effectFnOpportunity,
39068
- redundantSchemaTagIdentifier
39453
+ redundantSchemaTagIdentifier,
39454
+ schemaSyncInEffect,
39455
+ preferSchemaOverJson
39069
39456
  ];
39070
39457
 
39071
39458
  // src/cli/diagnostics.ts
@@ -39991,7 +40378,7 @@ var collectSourceFileExportedSymbols = (sourceFile, tsInstance, typeChecker, max
39991
40378
  }
39992
40379
  const exports2 = typeChecker.getExportsOfModule(moduleSymbol);
39993
40380
  const workQueue = exports2.map((s) => {
39994
- const declarations = s.getDeclarations();
40381
+ const declarations = s.declarations;
39995
40382
  const location = declarations && declarations.length > 0 ? getLocationFromDeclaration(declarations[0], tsInstance) : void 0;
39996
40383
  return [s, tsInstance.symbolName(s), location, 0];
39997
40384
  });
@@ -40153,7 +40540,7 @@ function collectExportedItems(sourceFile, tsInstance, typeChecker, maxSymbolDept
40153
40540
  maxSymbolDepth
40154
40541
  );
40155
40542
  for (const { description, location, name, symbol: symbol3, type: type2 } of exportedSymbols) {
40156
- const declarations = symbol3.getDeclarations();
40543
+ const declarations = symbol3.declarations;
40157
40544
  const declaration = declarations && declarations.length > 0 ? declarations[0] : sourceFile;
40158
40545
  const contextTagResult = yield* pipe(
40159
40546
  typeParser.contextTag(type2, declaration),