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