@effect/language-service 0.23.4 → 0.24.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 +11 -11
- package/index.js +247 -28
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/transform.js +249 -28
- package/transform.js.map +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ This package implements a TypeScript language service plugin that allows additio
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
1. `npm install @effect/language-service --save-dev` in your project
|
|
8
|
-
2.
|
|
8
|
+
2. Inside your tsconfig.json, you should add the plugin configuration as follows:
|
|
9
9
|
```jsonc
|
|
10
10
|
{
|
|
11
11
|
"compilerOptions": {
|
|
@@ -22,10 +22,10 @@ This package implements a TypeScript language service plugin that allows additio
|
|
|
22
22
|
3. Ensure that you have installed TypeScript locally in your project and set your editor to use your workspace TypeScript version.
|
|
23
23
|
|
|
24
24
|
- In VSCode you can do this by pressing "F1" and typing "TypeScript: Select TypeScript version". Then select "Use workspace version". If that option does not appear, TypeScript is not installed locally in your node_modules.
|
|
25
|
-
- In JetBrains you may have to disable the Vue language service, and
|
|
26
|
-
- In NVim with nvim-vtsls you should refer to [how to enable TypeScript plugins in
|
|
25
|
+
- In JetBrains you may have to disable the Vue language service, and choose the workspace version of TypeScript in the settings from the dropdown.
|
|
26
|
+
- In NVim with nvim-vtsls you should refer to [how to enable TypeScript plugins in vtsls](https://github.com/yioneko/vtsls?tab=readme-ov-file#typescript-plugin-not-activated)
|
|
27
27
|
|
|
28
|
-
And you're done! You'll now be able to use a set of
|
|
28
|
+
And you're done! You'll now be able to use a set of refactors and diagnostics that target Effect!
|
|
29
29
|
|
|
30
30
|
## Provided functionalities
|
|
31
31
|
|
|
@@ -33,7 +33,7 @@ And you're done! You'll now be able to use a set of refactor and diagnostics tha
|
|
|
33
33
|
|
|
34
34
|
- Show the extended type of the current Effect
|
|
35
35
|
- Hovering `yield\*` of `Effect.gen` will show the Effect type parameters
|
|
36
|
-
- Hovering a variable assignment of a type Layer, will show info on how each service got
|
|
36
|
+
- Hovering a variable assignment of a type Layer, will show info on how each service got involved
|
|
37
37
|
- Hovering a layer, will attempt to produce a graph
|
|
38
38
|
|
|
39
39
|
### Diagnostics
|
|
@@ -85,7 +85,7 @@ Few options can be provided alongside the initialization of the Language Service
|
|
|
85
85
|
"quickinfo": true, // controls quickinfo over Effect (default: true)
|
|
86
86
|
"completions": true, // controls Effect completions (default: true)
|
|
87
87
|
"goto": true, // controls Effect goto references (default: true)
|
|
88
|
-
"allowedDuplicatedPackages": [], // list of package names that
|
|
88
|
+
"allowedDuplicatedPackages": [], // list of package names that have effect in peer dependencies and are allowed to be duplicated (default: [])
|
|
89
89
|
"barrelImportPackages": [], // package names that should be preferred as imported from the top level barrel file (default: [])
|
|
90
90
|
"namespaceImportPackages": [] // package names that should be preferred as imported with namespace imports e.g. ["effect", "@effect/*"] (default: [])
|
|
91
91
|
}
|
|
@@ -94,9 +94,9 @@ Few options can be provided alongside the initialization of the Language Service
|
|
|
94
94
|
}
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
## Why diagnostics
|
|
97
|
+
## Why do diagnostics not appear at compile time?
|
|
98
98
|
|
|
99
|
-
TypeScript
|
|
99
|
+
TypeScript LSPs are loaded only while editing your files. That means that if you run `tsc` in your project, the plugin won't be loaded and you'll miss out on the Effect diagnostics.
|
|
100
100
|
|
|
101
101
|
HOWEVER, if you use `ts-patch` you can enable the transform as well to get the diagnostics also at compile time.
|
|
102
102
|
Your `tsconfig.json` should look like this:
|
|
@@ -117,7 +117,7 @@ Your `tsconfig.json` should look like this:
|
|
|
117
117
|
To get diagnostics you need to install `ts-patch` which will make it possible to run `tspc`.
|
|
118
118
|
|
|
119
119
|
Running `tspc` in your project will now also run the plugin and give you the diagnostics at compile time.
|
|
120
|
-
Effect diagnostics will be shown only after standard TypeScript diagnostics
|
|
120
|
+
Effect diagnostics will be shown only after standard TypeScript diagnostics have been satisfied.
|
|
121
121
|
|
|
122
122
|
```ts
|
|
123
123
|
$ npx tspc
|
|
@@ -165,9 +165,9 @@ or you can set the severity for the entire project in the global plugin configur
|
|
|
165
165
|
|
|
166
166
|
The Svelte LSP does not properly compose with other LSPs when using SvelteKit. So the Effect LSP should be loaded as last entry to ensure proper composition.
|
|
167
167
|
|
|
168
|
-
If you did not
|
|
168
|
+
If you did not install the Svelte LSP into your local project but instead through the Svelte VSCode extension, we recommend instead to install it locally and add it as the first entry. That way it won't be applied by the VSCode extension.
|
|
169
169
|
|
|
170
|
-
Your tsconfig should look like this:
|
|
170
|
+
Your tsconfig.json should look like this:
|
|
171
171
|
|
|
172
172
|
```jsonc
|
|
173
173
|
{
|
package/index.js
CHANGED
|
@@ -2162,22 +2162,16 @@ function make4(ts, typeChecker) {
|
|
|
2162
2162
|
);
|
|
2163
2163
|
const effectType = cachedBy(
|
|
2164
2164
|
fn("TypeParser.effectType")(function* (type, atLocation) {
|
|
2165
|
-
|
|
2165
|
+
let result = typeParserIssue("Type has no effect variance struct", type, atLocation);
|
|
2166
2166
|
const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
|
|
2167
|
-
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional)
|
|
2167
|
+
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration && ts.isPropertySignature(_.valueDeclaration) && ts.isComputedPropertyName(_.valueDeclaration.name)
|
|
2168
2168
|
);
|
|
2169
2169
|
propertiesSymbols.sort((a, b) => b.name.indexOf("EffectTypeId") - a.name.indexOf("EffectTypeId"));
|
|
2170
2170
|
for (const propertySymbol of propertiesSymbols) {
|
|
2171
2171
|
const propertyType = typeChecker.getTypeOfSymbolAtLocation(propertySymbol, atLocation);
|
|
2172
|
-
|
|
2173
|
-
propertyType,
|
|
2174
|
-
atLocation
|
|
2175
|
-
));
|
|
2176
|
-
if (isSome2(varianceArgs)) {
|
|
2177
|
-
return varianceArgs.value;
|
|
2178
|
-
}
|
|
2172
|
+
result = pipe(result, orElse2(() => effectVarianceStruct(propertyType, atLocation)));
|
|
2179
2173
|
}
|
|
2180
|
-
return yield*
|
|
2174
|
+
return yield* result;
|
|
2181
2175
|
}),
|
|
2182
2176
|
"TypeParser.effectType",
|
|
2183
2177
|
(type) => type
|
|
@@ -2196,7 +2190,7 @@ function make4(ts, typeChecker) {
|
|
|
2196
2190
|
fn("TypeParser.layerType")(function* (type, atLocation) {
|
|
2197
2191
|
yield* pipeableType(type, atLocation);
|
|
2198
2192
|
const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
|
|
2199
|
-
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional)
|
|
2193
|
+
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration && ts.isPropertySignature(_.valueDeclaration) && ts.isComputedPropertyName(_.valueDeclaration.name)
|
|
2200
2194
|
);
|
|
2201
2195
|
propertiesSymbols.sort((a, b) => b.name.indexOf("LayerTypeId") - a.name.indexOf("LayerTypeId"));
|
|
2202
2196
|
for (const propertySymbol of propertiesSymbols) {
|
|
@@ -2480,7 +2474,7 @@ function make4(ts, typeChecker) {
|
|
|
2480
2474
|
const ast = typeChecker.getPropertyOfType(type, "ast");
|
|
2481
2475
|
if (!ast) return yield* typeParserIssue("Has no 'ast' property", type, atLocation);
|
|
2482
2476
|
const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
|
|
2483
|
-
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional)
|
|
2477
|
+
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration && ts.isPropertySignature(_.valueDeclaration) && ts.isComputedPropertyName(_.valueDeclaration.name)
|
|
2484
2478
|
);
|
|
2485
2479
|
propertiesSymbols.sort((a, b) => b.name.indexOf("TypeId") - a.name.indexOf("TypeId"));
|
|
2486
2480
|
for (const propertySymbol of propertiesSymbols) {
|
|
@@ -2509,7 +2503,7 @@ function make4(ts, typeChecker) {
|
|
|
2509
2503
|
fn("TypeParser.contextTag")(function* (type, atLocation) {
|
|
2510
2504
|
yield* pipeableType(type, atLocation);
|
|
2511
2505
|
const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
|
|
2512
|
-
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional)
|
|
2506
|
+
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration && ts.isPropertySignature(_.valueDeclaration) && ts.isComputedPropertyName(_.valueDeclaration.name)
|
|
2513
2507
|
);
|
|
2514
2508
|
propertiesSymbols.sort((a, b) => b.name.indexOf("TypeId") - a.name.indexOf("TypeId"));
|
|
2515
2509
|
for (const propertySymbol of propertiesSymbols) {
|
|
@@ -2541,6 +2535,25 @@ function make4(ts, typeChecker) {
|
|
|
2541
2535
|
"TypeParser.pipeCall",
|
|
2542
2536
|
(node) => node
|
|
2543
2537
|
);
|
|
2538
|
+
const scopeType = cachedBy(
|
|
2539
|
+
fn("TypeParser.scopeType")(function* (type, atLocation) {
|
|
2540
|
+
yield* pipeableType(type, atLocation);
|
|
2541
|
+
const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
|
|
2542
|
+
(_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration && ts.isPropertySignature(_.valueDeclaration) && ts.isComputedPropertyName(_.valueDeclaration.name)
|
|
2543
|
+
);
|
|
2544
|
+
propertiesSymbols.sort((a, b) => b.name.indexOf("ScopeTypeId") - a.name.indexOf("ScopeTypeId"));
|
|
2545
|
+
for (const propertySymbol of propertiesSymbols) {
|
|
2546
|
+
const computedPropertyExpression = propertySymbol.valueDeclaration.name;
|
|
2547
|
+
const symbol3 = typeChecker.getSymbolAtLocation(computedPropertyExpression.expression);
|
|
2548
|
+
if (symbol3 && symbol3.name === "ScopeTypeId") {
|
|
2549
|
+
return type;
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
return yield* typeParserIssue("Type has no scope type id", type, atLocation);
|
|
2553
|
+
}),
|
|
2554
|
+
"TypeParser.scopeType",
|
|
2555
|
+
(type) => type
|
|
2556
|
+
);
|
|
2544
2557
|
return {
|
|
2545
2558
|
effectType,
|
|
2546
2559
|
strictEffectType,
|
|
@@ -2554,7 +2567,8 @@ function make4(ts, typeChecker) {
|
|
|
2554
2567
|
unnecessaryEffectGen: unnecessaryEffectGen2,
|
|
2555
2568
|
effectSchemaType,
|
|
2556
2569
|
contextTag,
|
|
2557
|
-
pipeCall
|
|
2570
|
+
pipeCall,
|
|
2571
|
+
scopeType
|
|
2558
2572
|
};
|
|
2559
2573
|
}
|
|
2560
2574
|
|
|
@@ -2687,6 +2701,8 @@ var importFromBarrel = createDiagnostic({
|
|
|
2687
2701
|
if (!moduleSymbol.exports) return;
|
|
2688
2702
|
const sourceFile2 = importDeclaration.getSourceFile();
|
|
2689
2703
|
const nodeForSymbol = element.propertyName || element.name;
|
|
2704
|
+
const aliasSymbol = element.name || element.propertyName;
|
|
2705
|
+
const aliasedName = aliasSymbol.text;
|
|
2690
2706
|
if (!ts.isIdentifier(nodeForSymbol)) return;
|
|
2691
2707
|
const importedName = nodeForSymbol.text;
|
|
2692
2708
|
const reexportedSymbol = moduleSymbol.exports.get(ts.escapeLeadingUnderscores(importedName));
|
|
@@ -2709,7 +2725,15 @@ var importFromBarrel = createDiagnostic({
|
|
|
2709
2725
|
program
|
|
2710
2726
|
);
|
|
2711
2727
|
if (unbarrelledFileName.toLowerCase().indexOf(barrelModuleName.toLowerCase() + "/") === -1) return;
|
|
2712
|
-
return {
|
|
2728
|
+
return {
|
|
2729
|
+
unbarrelledFileName,
|
|
2730
|
+
importedName,
|
|
2731
|
+
barrelModuleName,
|
|
2732
|
+
importClause,
|
|
2733
|
+
namedBindings,
|
|
2734
|
+
importDeclaration,
|
|
2735
|
+
aliasedName
|
|
2736
|
+
};
|
|
2713
2737
|
};
|
|
2714
2738
|
const nodeToVisit = [];
|
|
2715
2739
|
const appendNodeToVisit = (node) => {
|
|
@@ -2726,14 +2750,21 @@ var importFromBarrel = createDiagnostic({
|
|
|
2726
2750
|
}
|
|
2727
2751
|
const result = isImportedFromBarrelExport(node);
|
|
2728
2752
|
if (!result) continue;
|
|
2729
|
-
const {
|
|
2753
|
+
const {
|
|
2754
|
+
aliasedName,
|
|
2755
|
+
barrelModuleName,
|
|
2756
|
+
importClause,
|
|
2757
|
+
importDeclaration,
|
|
2758
|
+
namedBindings,
|
|
2759
|
+
unbarrelledFileName
|
|
2760
|
+
} = result;
|
|
2730
2761
|
report({
|
|
2731
2762
|
node,
|
|
2732
2763
|
messageText: `Importing from barrel module ${barrelModuleName} is not allowed.`,
|
|
2733
2764
|
fixes: [
|
|
2734
2765
|
{
|
|
2735
2766
|
fixName: "replaceWithUnbarrelledImport",
|
|
2736
|
-
description: `Import * as ${
|
|
2767
|
+
description: `Import * as ${aliasedName} from ${unbarrelledFileName}`,
|
|
2737
2768
|
apply: gen(function* () {
|
|
2738
2769
|
const changeTracker = yield* service(ChangeTracker);
|
|
2739
2770
|
const newImport = ts.factory.createImportDeclaration(
|
|
@@ -2741,7 +2772,7 @@ var importFromBarrel = createDiagnostic({
|
|
|
2741
2772
|
ts.factory.createImportClause(
|
|
2742
2773
|
importClause.isTypeOnly || node.isTypeOnly,
|
|
2743
2774
|
void 0,
|
|
2744
|
-
ts.factory.createNamespaceImport(ts.factory.createIdentifier(
|
|
2775
|
+
ts.factory.createNamespaceImport(ts.factory.createIdentifier(aliasedName))
|
|
2745
2776
|
),
|
|
2746
2777
|
ts.factory.createStringLiteral(unbarrelledFileName)
|
|
2747
2778
|
);
|
|
@@ -2930,18 +2961,39 @@ var missingEffectError = createDiagnostic({
|
|
|
2930
2961
|
code: 1,
|
|
2931
2962
|
severity: "error",
|
|
2932
2963
|
apply: fn("missingEffectError.apply")(function* (sourceFile, report) {
|
|
2964
|
+
const ts = yield* service(TypeScriptApi);
|
|
2933
2965
|
const typeChecker = yield* service(TypeCheckerApi);
|
|
2934
2966
|
const typeParser = yield* service(TypeParser);
|
|
2935
2967
|
const typeOrder = yield* deterministicTypeOrder;
|
|
2968
|
+
const effectModuleIdentifier = yield* pipe(
|
|
2969
|
+
findImportedModuleIdentifierByPackageAndNameOrBarrel(
|
|
2970
|
+
sourceFile,
|
|
2971
|
+
"effect",
|
|
2972
|
+
"Effect"
|
|
2973
|
+
),
|
|
2974
|
+
map4((_) => _.text),
|
|
2975
|
+
orElse2(() => succeed("Effect"))
|
|
2976
|
+
);
|
|
2977
|
+
const createDieMessage = (message) => ts.factory.createCallExpression(
|
|
2978
|
+
ts.factory.createPropertyAccessExpression(
|
|
2979
|
+
ts.factory.createIdentifier(effectModuleIdentifier),
|
|
2980
|
+
"dieMessage"
|
|
2981
|
+
),
|
|
2982
|
+
void 0,
|
|
2983
|
+
[ts.factory.createStringLiteral(message)]
|
|
2984
|
+
);
|
|
2936
2985
|
const checkForMissingErrorTypes = (node, expectedType, valueNode, realType) => pipe(
|
|
2937
2986
|
all(
|
|
2938
2987
|
typeParser.effectType(expectedType, node),
|
|
2939
2988
|
typeParser.effectType(realType, valueNode)
|
|
2940
2989
|
),
|
|
2941
2990
|
flatMap2(
|
|
2942
|
-
([expectedEffect, realEffect]) =>
|
|
2943
|
-
|
|
2944
|
-
|
|
2991
|
+
([expectedEffect, realEffect]) => pipe(
|
|
2992
|
+
getMissingTypeEntriesInTargetType(
|
|
2993
|
+
realEffect.E,
|
|
2994
|
+
expectedEffect.E
|
|
2995
|
+
),
|
|
2996
|
+
map4((missingErrorTypes) => ({ missingErrorTypes, expectedErrorType: expectedEffect.E }))
|
|
2945
2997
|
)
|
|
2946
2998
|
)
|
|
2947
2999
|
);
|
|
@@ -2956,15 +3008,79 @@ var missingEffectError = createDiagnostic({
|
|
|
2956
3008
|
valueNode,
|
|
2957
3009
|
realType
|
|
2958
3010
|
),
|
|
2959
|
-
map4(
|
|
2960
|
-
(
|
|
3011
|
+
map4((result) => {
|
|
3012
|
+
if (result.missingErrorTypes.length === 0) return;
|
|
3013
|
+
const fixes = [];
|
|
3014
|
+
if (ts.isExpression(valueNode) && result.expectedErrorType.flags & ts.TypeFlags.Never) {
|
|
3015
|
+
fixes.push({
|
|
3016
|
+
fixName: "missingEffectError_catchAll",
|
|
3017
|
+
description: "Catch all errors with Effect.catchAll",
|
|
3018
|
+
apply: gen(function* () {
|
|
3019
|
+
const changeTracker = yield* service(ChangeTracker);
|
|
3020
|
+
changeTracker.insertText(sourceFile, valueNode.getStart(), effectModuleIdentifier + ".catchAll(");
|
|
3021
|
+
changeTracker.insertText(sourceFile, valueNode.getEnd(), ", () => ");
|
|
3022
|
+
changeTracker.insertNodeAt(
|
|
3023
|
+
sourceFile,
|
|
3024
|
+
valueNode.getEnd(),
|
|
3025
|
+
createDieMessage("TODO: catchAll not implemented")
|
|
3026
|
+
);
|
|
3027
|
+
changeTracker.insertText(sourceFile, valueNode.getEnd(), ")");
|
|
3028
|
+
})
|
|
3029
|
+
});
|
|
3030
|
+
}
|
|
3031
|
+
if (ts.isExpression(valueNode)) {
|
|
3032
|
+
const propertyAssignments = pipe(
|
|
3033
|
+
result.missingErrorTypes,
|
|
3034
|
+
map3((_) => typeChecker.getPropertyOfType(_, "_tag")),
|
|
3035
|
+
filter((_) => !!_),
|
|
3036
|
+
map3((_) => typeChecker.getTypeOfSymbolAtLocation(_, valueNode)),
|
|
3037
|
+
filter((_) => !!(_.flags & ts.TypeFlags.Literal)),
|
|
3038
|
+
map3((_) => typeChecker.typeToTypeNode(_, void 0, ts.NodeBuilderFlags.NoTruncation)),
|
|
3039
|
+
filter((_) => !!_ && ts.isLiteralTypeNode(_)),
|
|
3040
|
+
map3((_) => _.literal),
|
|
3041
|
+
filter((_) => ts.isLiteralExpression(_)),
|
|
3042
|
+
map3((_) => _.text),
|
|
3043
|
+
sort(string2),
|
|
3044
|
+
map3(
|
|
3045
|
+
(_) => ts.factory.createPropertyAssignment(
|
|
3046
|
+
ts.factory.createIdentifier(_),
|
|
3047
|
+
ts.factory.createArrowFunction(
|
|
3048
|
+
void 0,
|
|
3049
|
+
void 0,
|
|
3050
|
+
[],
|
|
3051
|
+
void 0,
|
|
3052
|
+
void 0,
|
|
3053
|
+
createDieMessage(`TODO: catchTags() not implemented for ${_}`)
|
|
3054
|
+
)
|
|
3055
|
+
)
|
|
3056
|
+
)
|
|
3057
|
+
);
|
|
3058
|
+
if (propertyAssignments.length === result.missingErrorTypes.length) {
|
|
3059
|
+
fixes.push({
|
|
3060
|
+
fixName: "missingEffectError_tagged",
|
|
3061
|
+
description: "Catch unexpected errors with Effect.catchTag",
|
|
3062
|
+
apply: gen(function* () {
|
|
3063
|
+
const changeTracker = yield* service(ChangeTracker);
|
|
3064
|
+
changeTracker.insertText(sourceFile, valueNode.getStart(), effectModuleIdentifier + ".catchTags(");
|
|
3065
|
+
changeTracker.insertText(sourceFile, valueNode.getEnd(), ", ");
|
|
3066
|
+
changeTracker.insertNodeAt(
|
|
3067
|
+
sourceFile,
|
|
3068
|
+
valueNode.getEnd(),
|
|
3069
|
+
ts.factory.createObjectLiteralExpression(propertyAssignments)
|
|
3070
|
+
);
|
|
3071
|
+
changeTracker.insertText(sourceFile, valueNode.getEnd(), ")");
|
|
3072
|
+
})
|
|
3073
|
+
});
|
|
3074
|
+
}
|
|
3075
|
+
}
|
|
3076
|
+
report(
|
|
2961
3077
|
{
|
|
2962
3078
|
node,
|
|
2963
|
-
messageText: `Missing '${sortTypes(
|
|
2964
|
-
fixes
|
|
3079
|
+
messageText: `Missing '${sortTypes(result.missingErrorTypes).map((_) => typeChecker.typeToString(_)).join(" | ")}' in the expected Effect errors.`,
|
|
3080
|
+
fixes
|
|
2965
3081
|
}
|
|
2966
|
-
)
|
|
2967
|
-
),
|
|
3082
|
+
);
|
|
3083
|
+
}),
|
|
2968
3084
|
ignore
|
|
2969
3085
|
);
|
|
2970
3086
|
}
|
|
@@ -3176,6 +3292,108 @@ Nested Effect-able types may be intended if you plan to later manually flatten o
|
|
|
3176
3292
|
})
|
|
3177
3293
|
});
|
|
3178
3294
|
|
|
3295
|
+
// src/diagnostics/scopeInLayerEffect.ts
|
|
3296
|
+
var scopeInLayerEffect = createDiagnostic({
|
|
3297
|
+
name: "scopeInLayerEffect",
|
|
3298
|
+
code: 13,
|
|
3299
|
+
severity: "warning",
|
|
3300
|
+
apply: fn("scopeInLayerEffect.apply")(function* (sourceFile, report) {
|
|
3301
|
+
const ts = yield* service(TypeScriptApi);
|
|
3302
|
+
const typeChecker = yield* service(TypeCheckerApi);
|
|
3303
|
+
const typeParser = yield* service(TypeParser);
|
|
3304
|
+
const layerModuleIdentifier = yield* pipe(
|
|
3305
|
+
findImportedModuleIdentifierByPackageAndNameOrBarrel(
|
|
3306
|
+
sourceFile,
|
|
3307
|
+
"effect",
|
|
3308
|
+
"Layer"
|
|
3309
|
+
),
|
|
3310
|
+
map4((_) => _.text),
|
|
3311
|
+
orElse2(() => succeed("Layer"))
|
|
3312
|
+
);
|
|
3313
|
+
function parseLayerEffectApiCall(node) {
|
|
3314
|
+
if (!ts.isCallExpression(node)) return;
|
|
3315
|
+
const expression = node.expression;
|
|
3316
|
+
if (!ts.isPropertyAccessExpression(expression)) return;
|
|
3317
|
+
const calledModule = expression.expression;
|
|
3318
|
+
if (!(ts.isIdentifier(calledModule) && calledModule.text === layerModuleIdentifier)) return;
|
|
3319
|
+
const methodIdentifier = expression.name;
|
|
3320
|
+
if (!(ts.isIdentifier(methodIdentifier) && methodIdentifier.text.toLowerCase().startsWith("effect"))) return;
|
|
3321
|
+
return { methodIdentifier };
|
|
3322
|
+
}
|
|
3323
|
+
const reportIfLayerRequireScope = (type, node, methodIdentifier) => {
|
|
3324
|
+
let toCheck = [type];
|
|
3325
|
+
const entries = [];
|
|
3326
|
+
while (toCheck.length > 0) {
|
|
3327
|
+
const type2 = toCheck.pop();
|
|
3328
|
+
if (type2.isUnion()) {
|
|
3329
|
+
toCheck = toCheck.concat(type2.types);
|
|
3330
|
+
} else {
|
|
3331
|
+
entries.push(type2);
|
|
3332
|
+
}
|
|
3333
|
+
}
|
|
3334
|
+
return pipe(
|
|
3335
|
+
firstSuccessOf(entries.map((type2) => typeParser.scopeType(type2, node))),
|
|
3336
|
+
map4(
|
|
3337
|
+
() => report({
|
|
3338
|
+
node,
|
|
3339
|
+
messageText: `Seems like you are constructing a layer with a scope in the requirements.
|
|
3340
|
+
Consider using "scoped" instead to get ride of the scope in the requirements.`,
|
|
3341
|
+
fixes: methodIdentifier ? [{
|
|
3342
|
+
fixName: "scopeInLayerEffect_scoped",
|
|
3343
|
+
description: "Use scoped for Layer creation",
|
|
3344
|
+
apply: gen(function* () {
|
|
3345
|
+
const changeTracker = yield* service(ChangeTracker);
|
|
3346
|
+
changeTracker.replaceNode(
|
|
3347
|
+
sourceFile,
|
|
3348
|
+
methodIdentifier,
|
|
3349
|
+
ts.factory.createIdentifier("scoped")
|
|
3350
|
+
);
|
|
3351
|
+
})
|
|
3352
|
+
}] : []
|
|
3353
|
+
})
|
|
3354
|
+
),
|
|
3355
|
+
ignore
|
|
3356
|
+
);
|
|
3357
|
+
};
|
|
3358
|
+
const nodeToVisit = [];
|
|
3359
|
+
const appendNodeToVisit = (node) => {
|
|
3360
|
+
nodeToVisit.push(node);
|
|
3361
|
+
return void 0;
|
|
3362
|
+
};
|
|
3363
|
+
ts.forEachChild(sourceFile, appendNodeToVisit);
|
|
3364
|
+
while (nodeToVisit.length > 0) {
|
|
3365
|
+
const node = nodeToVisit.shift();
|
|
3366
|
+
const layerEffectApiCall = parseLayerEffectApiCall(node);
|
|
3367
|
+
if (layerEffectApiCall) {
|
|
3368
|
+
const type = typeChecker.getTypeAtLocation(node);
|
|
3369
|
+
yield* pipe(
|
|
3370
|
+
typeParser.layerType(type, node),
|
|
3371
|
+
flatMap2(({ RIn }) => reportIfLayerRequireScope(RIn, node, layerEffectApiCall.methodIdentifier)),
|
|
3372
|
+
ignore
|
|
3373
|
+
);
|
|
3374
|
+
continue;
|
|
3375
|
+
}
|
|
3376
|
+
if (ts.isClassDeclaration(node) && node.name && node.heritageClauses) {
|
|
3377
|
+
const classSym = typeChecker.getSymbolAtLocation(node.name);
|
|
3378
|
+
if (classSym) {
|
|
3379
|
+
const classType = typeChecker.getTypeOfSymbol(classSym);
|
|
3380
|
+
const defaultLayer = typeChecker.getPropertyOfType(classType, "Default");
|
|
3381
|
+
if (defaultLayer) {
|
|
3382
|
+
const type = typeChecker.getTypeOfSymbolAtLocation(defaultLayer, node);
|
|
3383
|
+
yield* pipe(
|
|
3384
|
+
typeParser.layerType(type, node),
|
|
3385
|
+
flatMap2(({ RIn }) => reportIfLayerRequireScope(RIn, node, void 0)),
|
|
3386
|
+
ignore
|
|
3387
|
+
);
|
|
3388
|
+
continue;
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
ts.forEachChild(node, appendNodeToVisit);
|
|
3393
|
+
}
|
|
3394
|
+
})
|
|
3395
|
+
});
|
|
3396
|
+
|
|
3179
3397
|
// src/diagnostics/unnecessaryEffectGen.ts
|
|
3180
3398
|
var unnecessaryEffectGen = createDiagnostic({
|
|
3181
3399
|
name: "unnecessaryEffectGen",
|
|
@@ -3277,7 +3495,8 @@ var diagnostics = [
|
|
|
3277
3495
|
unnecessaryPipe,
|
|
3278
3496
|
genericEffectServices,
|
|
3279
3497
|
returnEffectInGen,
|
|
3280
|
-
importFromBarrel
|
|
3498
|
+
importFromBarrel,
|
|
3499
|
+
scopeInLayerEffect
|
|
3281
3500
|
];
|
|
3282
3501
|
|
|
3283
3502
|
// src/completions/effectDiagnosticsComment.ts
|