@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/README.md +2 -0
- package/cli.js +231 -11
- package/cli.js.map +1 -1
- package/effect-lsp-patch-utils.js +173 -4
- package/effect-lsp-patch-utils.js.map +1 -1
- package/index.js +173 -4
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/transform.js +173 -4
- package/transform.js.map +1 -1
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: `
|
|
9305
|
-
|
|
9306
|
-
|
|
9307
|
-
|
|
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,
|