@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/package.json
CHANGED
package/transform.js
CHANGED
|
@@ -4851,6 +4851,104 @@ ${versions.map((version) => `- found ${version} at ${resolvedPackages[packageNam
|
|
|
4851
4851
|
})
|
|
4852
4852
|
});
|
|
4853
4853
|
|
|
4854
|
+
// src/diagnostics/effectFnIife.ts
|
|
4855
|
+
var effectFnIife = createDiagnostic({
|
|
4856
|
+
name: "effectFnIife",
|
|
4857
|
+
code: 46,
|
|
4858
|
+
description: "Effect.fn or Effect.fnUntraced is called as an IIFE (Immediately Invoked Function Expression). Use Effect.gen instead.",
|
|
4859
|
+
severity: "warning",
|
|
4860
|
+
apply: fn("effectFnIife.apply")(function* (sourceFile, report) {
|
|
4861
|
+
const ts = yield* service(TypeScriptApi);
|
|
4862
|
+
const typeParser = yield* service(TypeParser);
|
|
4863
|
+
const tsUtils = yield* service(TypeScriptUtils);
|
|
4864
|
+
const sourceEffectModuleName = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
|
|
4865
|
+
sourceFile,
|
|
4866
|
+
"effect",
|
|
4867
|
+
"Effect"
|
|
4868
|
+
) || "Effect";
|
|
4869
|
+
const nodeToVisit = [];
|
|
4870
|
+
const appendNodeToVisit = (node) => {
|
|
4871
|
+
nodeToVisit.push(node);
|
|
4872
|
+
return void 0;
|
|
4873
|
+
};
|
|
4874
|
+
ts.forEachChild(sourceFile, appendNodeToVisit);
|
|
4875
|
+
while (nodeToVisit.length > 0) {
|
|
4876
|
+
const node = nodeToVisit.shift();
|
|
4877
|
+
ts.forEachChild(node, appendNodeToVisit);
|
|
4878
|
+
if (!ts.isCallExpression(node)) continue;
|
|
4879
|
+
const innerCall = node.expression;
|
|
4880
|
+
if (!ts.isCallExpression(innerCall)) continue;
|
|
4881
|
+
const parsed = yield* pipe(
|
|
4882
|
+
typeParser.effectFnGen(innerCall),
|
|
4883
|
+
map4((result) => ({
|
|
4884
|
+
kind: "fn",
|
|
4885
|
+
effectModule: result.effectModule,
|
|
4886
|
+
generatorFunction: result.generatorFunction,
|
|
4887
|
+
pipeArguments: result.pipeArguments
|
|
4888
|
+
})),
|
|
4889
|
+
orElse2(
|
|
4890
|
+
() => pipe(
|
|
4891
|
+
typeParser.effectFnUntracedGen(innerCall),
|
|
4892
|
+
map4((result) => ({
|
|
4893
|
+
kind: "fnUntraced",
|
|
4894
|
+
effectModule: result.effectModule,
|
|
4895
|
+
generatorFunction: result.generatorFunction,
|
|
4896
|
+
pipeArguments: result.pipeArguments
|
|
4897
|
+
}))
|
|
4898
|
+
)
|
|
4899
|
+
),
|
|
4900
|
+
orElse2(
|
|
4901
|
+
() => pipe(
|
|
4902
|
+
typeParser.effectFn(innerCall),
|
|
4903
|
+
map4((result) => ({
|
|
4904
|
+
kind: "fn",
|
|
4905
|
+
effectModule: result.effectModule,
|
|
4906
|
+
generatorFunction: void 0,
|
|
4907
|
+
pipeArguments: result.pipeArguments
|
|
4908
|
+
}))
|
|
4909
|
+
)
|
|
4910
|
+
),
|
|
4911
|
+
option
|
|
4912
|
+
);
|
|
4913
|
+
if (isNone2(parsed)) continue;
|
|
4914
|
+
const { effectModule, generatorFunction, kind, pipeArguments: pipeArguments2 } = parsed.value;
|
|
4915
|
+
const effectModuleName = ts.isIdentifier(effectModule) ? ts.idText(effectModule) : sourceEffectModuleName;
|
|
4916
|
+
const fixes = [];
|
|
4917
|
+
if (generatorFunction && generatorFunction.parameters.length === 0) {
|
|
4918
|
+
fixes.push({
|
|
4919
|
+
fixName: "effectFnIife_toEffectGen",
|
|
4920
|
+
description: "Convert to Effect.gen",
|
|
4921
|
+
apply: gen(function* () {
|
|
4922
|
+
const changeTracker = yield* service(ChangeTracker);
|
|
4923
|
+
const effectGenCall = ts.factory.createCallExpression(
|
|
4924
|
+
ts.factory.createPropertyAccessExpression(
|
|
4925
|
+
ts.factory.createIdentifier(effectModuleName),
|
|
4926
|
+
"gen"
|
|
4927
|
+
),
|
|
4928
|
+
void 0,
|
|
4929
|
+
[generatorFunction]
|
|
4930
|
+
);
|
|
4931
|
+
let replacementNode = effectGenCall;
|
|
4932
|
+
if (pipeArguments2.length > 0) {
|
|
4933
|
+
replacementNode = ts.factory.createCallExpression(
|
|
4934
|
+
ts.factory.createPropertyAccessExpression(effectGenCall, "pipe"),
|
|
4935
|
+
void 0,
|
|
4936
|
+
[...pipeArguments2]
|
|
4937
|
+
);
|
|
4938
|
+
}
|
|
4939
|
+
changeTracker.replaceNode(sourceFile, node, replacementNode);
|
|
4940
|
+
})
|
|
4941
|
+
});
|
|
4942
|
+
}
|
|
4943
|
+
report({
|
|
4944
|
+
location: node,
|
|
4945
|
+
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).`,
|
|
4946
|
+
fixes
|
|
4947
|
+
});
|
|
4948
|
+
}
|
|
4949
|
+
})
|
|
4950
|
+
});
|
|
4951
|
+
|
|
4854
4952
|
// src/diagnostics/effectFnOpportunity.ts
|
|
4855
4953
|
var effectFnOpportunity = createDiagnostic({
|
|
4856
4954
|
name: "effectFnOpportunity",
|
|
@@ -5618,6 +5716,69 @@ var importFromBarrel = createDiagnostic({
|
|
|
5618
5716
|
})
|
|
5619
5717
|
});
|
|
5620
5718
|
|
|
5719
|
+
// src/diagnostics/instanceOfSchema.ts
|
|
5720
|
+
var instanceOfSchema = createDiagnostic({
|
|
5721
|
+
name: "instanceOfSchema",
|
|
5722
|
+
code: 45,
|
|
5723
|
+
description: "Suggests using Schema.is instead of instanceof for Effect Schema types",
|
|
5724
|
+
severity: "off",
|
|
5725
|
+
apply: fn("instanceOfSchema.apply")(function* (sourceFile, report) {
|
|
5726
|
+
const ts = yield* service(TypeScriptApi);
|
|
5727
|
+
const typeParser = yield* service(TypeParser);
|
|
5728
|
+
const typeCheckerUtils = yield* service(TypeCheckerUtils);
|
|
5729
|
+
const nodeToVisit = [];
|
|
5730
|
+
const appendNodeToVisit = (node) => {
|
|
5731
|
+
nodeToVisit.push(node);
|
|
5732
|
+
return void 0;
|
|
5733
|
+
};
|
|
5734
|
+
ts.forEachChild(sourceFile, appendNodeToVisit);
|
|
5735
|
+
while (nodeToVisit.length > 0) {
|
|
5736
|
+
const node = nodeToVisit.shift();
|
|
5737
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.InstanceOfKeyword) {
|
|
5738
|
+
const leftExpr = node.left;
|
|
5739
|
+
const rightExpr = node.right;
|
|
5740
|
+
const rightType = typeCheckerUtils.getTypeAtLocation(rightExpr);
|
|
5741
|
+
if (!rightType) {
|
|
5742
|
+
ts.forEachChild(node, appendNodeToVisit);
|
|
5743
|
+
continue;
|
|
5744
|
+
}
|
|
5745
|
+
const isSchemaType = yield* pipe(
|
|
5746
|
+
typeParser.effectSchemaType(rightType, rightExpr),
|
|
5747
|
+
option
|
|
5748
|
+
);
|
|
5749
|
+
if (isSchemaType._tag === "Some") {
|
|
5750
|
+
report({
|
|
5751
|
+
location: node,
|
|
5752
|
+
messageText: "Consider using Schema.is instead of instanceof for Effect Schema types.",
|
|
5753
|
+
fixes: [{
|
|
5754
|
+
fixName: "instanceOfSchema_fix",
|
|
5755
|
+
description: "Replace with Schema.is",
|
|
5756
|
+
apply: gen(function* () {
|
|
5757
|
+
const changeTracker = yield* service(ChangeTracker);
|
|
5758
|
+
const schemaIsCall = ts.factory.createCallExpression(
|
|
5759
|
+
ts.factory.createPropertyAccessExpression(
|
|
5760
|
+
ts.factory.createIdentifier("Schema"),
|
|
5761
|
+
"is"
|
|
5762
|
+
),
|
|
5763
|
+
void 0,
|
|
5764
|
+
[rightExpr]
|
|
5765
|
+
);
|
|
5766
|
+
const fullCall = ts.factory.createCallExpression(
|
|
5767
|
+
schemaIsCall,
|
|
5768
|
+
void 0,
|
|
5769
|
+
[leftExpr]
|
|
5770
|
+
);
|
|
5771
|
+
changeTracker.replaceNode(sourceFile, node, fullCall);
|
|
5772
|
+
})
|
|
5773
|
+
}]
|
|
5774
|
+
});
|
|
5775
|
+
}
|
|
5776
|
+
}
|
|
5777
|
+
ts.forEachChild(node, appendNodeToVisit);
|
|
5778
|
+
}
|
|
5779
|
+
})
|
|
5780
|
+
});
|
|
5781
|
+
|
|
5621
5782
|
// src/diagnostics/layerMergeAllWithDependencies.ts
|
|
5622
5783
|
var layerMergeAllWithDependencies = createDiagnostic({
|
|
5623
5784
|
name: "layerMergeAllWithDependencies",
|
|
@@ -5825,12 +5986,18 @@ var leakingRequirements = createDiagnostic({
|
|
|
5825
5986
|
);
|
|
5826
5987
|
function reportLeakingRequirements(node, requirements) {
|
|
5827
5988
|
if (requirements.length === 0) return;
|
|
5989
|
+
const requirementsStr = requirements.map((_) => typeChecker.typeToString(_)).join(" | ");
|
|
5828
5990
|
report({
|
|
5829
5991
|
location: node,
|
|
5830
|
-
messageText: `
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5992
|
+
messageText: `Methods of this Service require \`${requirementsStr}\` from every caller.
|
|
5993
|
+
|
|
5994
|
+
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.
|
|
5995
|
+
|
|
5996
|
+
Resolve these dependencies at Layer creation and provide them to each method, so the service's type reflects its purpose, not its implementation.
|
|
5997
|
+
|
|
5998
|
+
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.
|
|
5999
|
+
|
|
6000
|
+
More info and examples at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage`,
|
|
5834
6001
|
fixes: []
|
|
5835
6002
|
});
|
|
5836
6003
|
}
|
|
@@ -9041,6 +9208,7 @@ var unsupportedServiceAccessors = createDiagnostic({
|
|
|
9041
9208
|
// src/diagnostics.ts
|
|
9042
9209
|
var diagnostics = [
|
|
9043
9210
|
anyUnknownInErrorContext,
|
|
9211
|
+
instanceOfSchema,
|
|
9044
9212
|
catchAllToMapError,
|
|
9045
9213
|
catchUnfailableEffect,
|
|
9046
9214
|
classSelfMismatch,
|
|
@@ -9081,6 +9249,7 @@ var diagnostics = [
|
|
|
9081
9249
|
globalErrorInEffectFailure,
|
|
9082
9250
|
layerMergeAllWithDependencies,
|
|
9083
9251
|
effectMapVoid,
|
|
9252
|
+
effectFnIife,
|
|
9084
9253
|
effectFnOpportunity,
|
|
9085
9254
|
redundantSchemaTagIdentifier,
|
|
9086
9255
|
schemaSyncInEffect,
|