@effect/language-service 0.67.0 → 0.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -8325,6 +8325,104 @@ ${versions.map((version) => `- found ${version} at ${resolvedPackages[packageNam
8325
8325
  })
8326
8326
  });
8327
8327
 
8328
+ // src/diagnostics/effectFnIife.ts
8329
+ var effectFnIife = createDiagnostic({
8330
+ name: "effectFnIife",
8331
+ code: 46,
8332
+ description: "Effect.fn or Effect.fnUntraced is called as an IIFE (Immediately Invoked Function Expression). Use Effect.gen instead.",
8333
+ severity: "warning",
8334
+ apply: fn("effectFnIife.apply")(function* (sourceFile, report) {
8335
+ const ts = yield* service(TypeScriptApi);
8336
+ const typeParser = yield* service(TypeParser);
8337
+ const tsUtils = yield* service(TypeScriptUtils);
8338
+ const sourceEffectModuleName = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
8339
+ sourceFile,
8340
+ "effect",
8341
+ "Effect"
8342
+ ) || "Effect";
8343
+ const nodeToVisit = [];
8344
+ const appendNodeToVisit = (node) => {
8345
+ nodeToVisit.push(node);
8346
+ return void 0;
8347
+ };
8348
+ ts.forEachChild(sourceFile, appendNodeToVisit);
8349
+ while (nodeToVisit.length > 0) {
8350
+ const node = nodeToVisit.shift();
8351
+ ts.forEachChild(node, appendNodeToVisit);
8352
+ if (!ts.isCallExpression(node)) continue;
8353
+ const innerCall = node.expression;
8354
+ if (!ts.isCallExpression(innerCall)) continue;
8355
+ const parsed = yield* pipe(
8356
+ typeParser.effectFnGen(innerCall),
8357
+ map8((result) => ({
8358
+ kind: "fn",
8359
+ effectModule: result.effectModule,
8360
+ generatorFunction: result.generatorFunction,
8361
+ pipeArguments: result.pipeArguments
8362
+ })),
8363
+ orElse2(
8364
+ () => pipe(
8365
+ typeParser.effectFnUntracedGen(innerCall),
8366
+ map8((result) => ({
8367
+ kind: "fnUntraced",
8368
+ effectModule: result.effectModule,
8369
+ generatorFunction: result.generatorFunction,
8370
+ pipeArguments: result.pipeArguments
8371
+ }))
8372
+ )
8373
+ ),
8374
+ orElse2(
8375
+ () => pipe(
8376
+ typeParser.effectFn(innerCall),
8377
+ map8((result) => ({
8378
+ kind: "fn",
8379
+ effectModule: result.effectModule,
8380
+ generatorFunction: void 0,
8381
+ pipeArguments: result.pipeArguments
8382
+ }))
8383
+ )
8384
+ ),
8385
+ option
8386
+ );
8387
+ if (isNone2(parsed)) continue;
8388
+ const { effectModule, generatorFunction, kind, pipeArguments: pipeArguments2 } = parsed.value;
8389
+ const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
8390
+ const fixes = [];
8391
+ if (generatorFunction && generatorFunction.parameters.length === 0) {
8392
+ fixes.push({
8393
+ fixName: "effectFnIife_toEffectGen",
8394
+ description: "Convert to Effect.gen",
8395
+ apply: gen(function* () {
8396
+ const changeTracker = yield* service(ChangeTracker);
8397
+ const effectGenCall = ts.factory.createCallExpression(
8398
+ ts.factory.createPropertyAccessExpression(
8399
+ ts.factory.createIdentifier(effectModuleName),
8400
+ "gen"
8401
+ ),
8402
+ void 0,
8403
+ [generatorFunction]
8404
+ );
8405
+ let replacementNode = effectGenCall;
8406
+ if (pipeArguments2.length > 0) {
8407
+ replacementNode = ts.factory.createCallExpression(
8408
+ ts.factory.createPropertyAccessExpression(effectGenCall, "pipe"),
8409
+ void 0,
8410
+ [...pipeArguments2]
8411
+ );
8412
+ }
8413
+ changeTracker.replaceNode(sourceFile, node, replacementNode);
8414
+ })
8415
+ });
8416
+ }
8417
+ report({
8418
+ location: node,
8419
+ messageText: `${effectModuleName}.${kind} returns a reusable function that can take arguments, but here it's called immediately. Use Effect.gen instead (optionally with Effect.withSpan for tracing).`,
8420
+ fixes
8421
+ });
8422
+ }
8423
+ })
8424
+ });
8425
+
8328
8426
  // src/diagnostics/effectFnOpportunity.ts
8329
8427
  var effectFnOpportunity = createDiagnostic({
8330
8428
  name: "effectFnOpportunity",
@@ -9092,6 +9190,69 @@ var importFromBarrel = createDiagnostic({
9092
9190
  })
9093
9191
  });
9094
9192
 
9193
+ // src/diagnostics/instanceOfSchema.ts
9194
+ var instanceOfSchema = createDiagnostic({
9195
+ name: "instanceOfSchema",
9196
+ code: 45,
9197
+ description: "Suggests using Schema.is instead of instanceof for Effect Schema types",
9198
+ severity: "off",
9199
+ apply: fn("instanceOfSchema.apply")(function* (sourceFile, report) {
9200
+ const ts = yield* service(TypeScriptApi);
9201
+ const typeParser = yield* service(TypeParser);
9202
+ const typeCheckerUtils = yield* service(TypeCheckerUtils);
9203
+ const nodeToVisit = [];
9204
+ const appendNodeToVisit = (node) => {
9205
+ nodeToVisit.push(node);
9206
+ return void 0;
9207
+ };
9208
+ ts.forEachChild(sourceFile, appendNodeToVisit);
9209
+ while (nodeToVisit.length > 0) {
9210
+ const node = nodeToVisit.shift();
9211
+ if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) {
9212
+ const leftExpr = node.left;
9213
+ const rightExpr = node.right;
9214
+ const rightType = typeCheckerUtils.getTypeAtLocation(rightExpr);
9215
+ if (!rightType) {
9216
+ ts.forEachChild(node, appendNodeToVisit);
9217
+ continue;
9218
+ }
9219
+ const isSchemaType = yield* pipe(
9220
+ typeParser.effectSchemaType(rightType, rightExpr),
9221
+ option
9222
+ );
9223
+ if (isSchemaType._tag === "Some") {
9224
+ report({
9225
+ location: node,
9226
+ messageText: "Consider using Schema.is instead of instanceof for Effect Schema types.",
9227
+ fixes: [{
9228
+ fixName: "instanceOfSchema_fix",
9229
+ description: "Replace with Schema.is",
9230
+ apply: gen(function* () {
9231
+ const changeTracker = yield* service(ChangeTracker);
9232
+ const schemaIsCall = ts.factory.createCallExpression(
9233
+ ts.factory.createPropertyAccessExpression(
9234
+ ts.factory.createIdentifier("Schema"),
9235
+ "is"
9236
+ ),
9237
+ void 0,
9238
+ [rightExpr]
9239
+ );
9240
+ const fullCall = ts.factory.createCallExpression(
9241
+ schemaIsCall,
9242
+ void 0,
9243
+ [leftExpr]
9244
+ );
9245
+ changeTracker.replaceNode(sourceFile, node, fullCall);
9246
+ })
9247
+ }]
9248
+ });
9249
+ }
9250
+ }
9251
+ ts.forEachChild(node, appendNodeToVisit);
9252
+ }
9253
+ })
9254
+ });
9255
+
9095
9256
  // src/diagnostics/layerMergeAllWithDependencies.ts
9096
9257
  var layerMergeAllWithDependencies = createDiagnostic({
9097
9258
  name: "layerMergeAllWithDependencies",
@@ -9299,12 +9460,18 @@ var leakingRequirements = createDiagnostic({
9299
9460
  );
9300
9461
  function reportLeakingRequirements(node, requirements) {
9301
9462
  if (requirements.length === 0) return;
9463
+ const requirementsStr = requirements.map((_) => typeChecker.typeToString(_)).join(" | ");
9302
9464
  report({
9303
9465
  location: node,
9304
- messageText: `This Service is leaking the ${requirements.map((_) => typeChecker.typeToString(_)).join(" | ")} requirement.
9305
- If these requirements cannot be cached and are expected to be provided per method invocation (e.g. HttpServerRequest), you can either safely disable this diagnostic for this line through quickfixes or mark the service declaration with a JSDoc @effect-leakable-service.
9306
- Services should usually be collected in the layer creation body, and then provided at each method that requires them.
9307
- More info at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage`,
9466
+ messageText: `Methods of this Service require \`${requirementsStr}\` from every caller.
9467
+
9468
+ This leaks implementation details into the service's public type \u2014 callers shouldn't need to know *how* the service works internally, only *what* it provides.
9469
+
9470
+ Resolve these dependencies at Layer creation and provide them to each method, so the service's type reflects its purpose, not its implementation.
9471
+
9472
+ To suppress this diagnostic for specific dependency types that are intentionally passed through (e.g., HttpServerRequest), add \`@effect-leakable-service\` JSDoc to their interface declarations (e.g., the \`${typeChecker.typeToString(requirements[0])}\` interface), not to this service.
9473
+
9474
+ More info and examples at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage`,
9308
9475
  fixes: []
9309
9476
  });
9310
9477
  }
@@ -11482,6 +11649,7 @@ var unsupportedServiceAccessors = createDiagnostic({
11482
11649
  // src/diagnostics.ts
11483
11650
  var diagnostics = [
11484
11651
  anyUnknownInErrorContext,
11652
+ instanceOfSchema,
11485
11653
  catchAllToMapError,
11486
11654
  catchUnfailableEffect,
11487
11655
  classSelfMismatch,
@@ -11522,6 +11690,7 @@ var diagnostics = [
11522
11690
  globalErrorInEffectFailure,
11523
11691
  layerMergeAllWithDependencies,
11524
11692
  effectMapVoid,
11693
+ effectFnIife,
11525
11694
  effectFnOpportunity,
11526
11695
  redundantSchemaTagIdentifier,
11527
11696
  schemaSyncInEffect,