@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect/language-service",
3
- "version": "0.23.4",
3
+ "version": "0.24.0",
4
4
  "description": "A Language-Service Plugin to Refactor and Diagnostic effect-ts projects",
5
5
  "main": "index.cjs",
6
6
  "repository": {
package/transform.js CHANGED
@@ -739,6 +739,7 @@ var isNonEmptyArray = (self) => self.length > 0;
739
739
 
740
740
  // node_modules/.pnpm/effect@3.16.5/node_modules/effect/dist/esm/Order.js
741
741
  var make = (compare) => (self, that) => self === that ? 0 : compare(self, that);
742
+ var string2 = /* @__PURE__ */ make((self, that) => self < that ? -1 : 1);
742
743
 
743
744
  // node_modules/.pnpm/effect@3.16.5/node_modules/effect/dist/esm/Option.js
744
745
  var none2 = () => none;
@@ -921,6 +922,7 @@ var orElse2 = (f) => (fa) => make2((ctx) => {
921
922
  if (result._tag === "Left") return f(result.value).run(ctx);
922
923
  return result;
923
924
  });
925
+ var firstSuccessOf = (arr) => arr.slice(1).reduce((arr2, fa) => orElse2(() => fa)(arr2), arr[0]);
924
926
  var service = (tag) => make2(
925
927
  (ctx) => tag.key in ctx.value ? ctx.value[tag.key] : makeInternalDefect(`Cannot find service ${tag.key}`)
926
928
  );
@@ -2027,22 +2029,16 @@ function make3(ts, typeChecker) {
2027
2029
  );
2028
2030
  const effectType = cachedBy(
2029
2031
  fn("TypeParser.effectType")(function* (type, atLocation) {
2030
- yield* pipeableType(type, atLocation);
2032
+ let result = typeParserIssue("Type has no effect variance struct", type, atLocation);
2031
2033
  const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
2032
- (_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional)
2034
+ (_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration && ts.isPropertySignature(_.valueDeclaration) && ts.isComputedPropertyName(_.valueDeclaration.name)
2033
2035
  );
2034
2036
  propertiesSymbols.sort((a, b) => b.name.indexOf("EffectTypeId") - a.name.indexOf("EffectTypeId"));
2035
2037
  for (const propertySymbol of propertiesSymbols) {
2036
2038
  const propertyType = typeChecker.getTypeOfSymbolAtLocation(propertySymbol, atLocation);
2037
- const varianceArgs = yield* option(effectVarianceStruct(
2038
- propertyType,
2039
- atLocation
2040
- ));
2041
- if (isSome2(varianceArgs)) {
2042
- return varianceArgs.value;
2043
- }
2039
+ result = pipe(result, orElse2(() => effectVarianceStruct(propertyType, atLocation)));
2044
2040
  }
2045
- return yield* typeParserIssue("Type has no effect variance struct", type, atLocation);
2041
+ return yield* result;
2046
2042
  }),
2047
2043
  "TypeParser.effectType",
2048
2044
  (type) => type
@@ -2061,7 +2057,7 @@ function make3(ts, typeChecker) {
2061
2057
  fn("TypeParser.layerType")(function* (type, atLocation) {
2062
2058
  yield* pipeableType(type, atLocation);
2063
2059
  const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
2064
- (_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional)
2060
+ (_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration && ts.isPropertySignature(_.valueDeclaration) && ts.isComputedPropertyName(_.valueDeclaration.name)
2065
2061
  );
2066
2062
  propertiesSymbols.sort((a, b) => b.name.indexOf("LayerTypeId") - a.name.indexOf("LayerTypeId"));
2067
2063
  for (const propertySymbol of propertiesSymbols) {
@@ -2345,7 +2341,7 @@ function make3(ts, typeChecker) {
2345
2341
  const ast = typeChecker.getPropertyOfType(type, "ast");
2346
2342
  if (!ast) return yield* typeParserIssue("Has no 'ast' property", type, atLocation);
2347
2343
  const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
2348
- (_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional)
2344
+ (_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration && ts.isPropertySignature(_.valueDeclaration) && ts.isComputedPropertyName(_.valueDeclaration.name)
2349
2345
  );
2350
2346
  propertiesSymbols.sort((a, b) => b.name.indexOf("TypeId") - a.name.indexOf("TypeId"));
2351
2347
  for (const propertySymbol of propertiesSymbols) {
@@ -2374,7 +2370,7 @@ function make3(ts, typeChecker) {
2374
2370
  fn("TypeParser.contextTag")(function* (type, atLocation) {
2375
2371
  yield* pipeableType(type, atLocation);
2376
2372
  const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
2377
- (_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional)
2373
+ (_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration && ts.isPropertySignature(_.valueDeclaration) && ts.isComputedPropertyName(_.valueDeclaration.name)
2378
2374
  );
2379
2375
  propertiesSymbols.sort((a, b) => b.name.indexOf("TypeId") - a.name.indexOf("TypeId"));
2380
2376
  for (const propertySymbol of propertiesSymbols) {
@@ -2406,6 +2402,25 @@ function make3(ts, typeChecker) {
2406
2402
  "TypeParser.pipeCall",
2407
2403
  (node) => node
2408
2404
  );
2405
+ const scopeType = cachedBy(
2406
+ fn("TypeParser.scopeType")(function* (type, atLocation) {
2407
+ yield* pipeableType(type, atLocation);
2408
+ const propertiesSymbols = typeChecker.getPropertiesOfType(type).filter(
2409
+ (_) => _.flags & ts.SymbolFlags.Property && !(_.flags & ts.SymbolFlags.Optional) && _.valueDeclaration && ts.isPropertySignature(_.valueDeclaration) && ts.isComputedPropertyName(_.valueDeclaration.name)
2410
+ );
2411
+ propertiesSymbols.sort((a, b) => b.name.indexOf("ScopeTypeId") - a.name.indexOf("ScopeTypeId"));
2412
+ for (const propertySymbol of propertiesSymbols) {
2413
+ const computedPropertyExpression = propertySymbol.valueDeclaration.name;
2414
+ const symbol3 = typeChecker.getSymbolAtLocation(computedPropertyExpression.expression);
2415
+ if (symbol3 && symbol3.name === "ScopeTypeId") {
2416
+ return type;
2417
+ }
2418
+ }
2419
+ return yield* typeParserIssue("Type has no scope type id", type, atLocation);
2420
+ }),
2421
+ "TypeParser.scopeType",
2422
+ (type) => type
2423
+ );
2409
2424
  return {
2410
2425
  effectType,
2411
2426
  strictEffectType,
@@ -2419,7 +2434,8 @@ function make3(ts, typeChecker) {
2419
2434
  unnecessaryEffectGen: unnecessaryEffectGen2,
2420
2435
  effectSchemaType,
2421
2436
  contextTag,
2422
- pipeCall
2437
+ pipeCall,
2438
+ scopeType
2423
2439
  };
2424
2440
  }
2425
2441
 
@@ -2600,6 +2616,8 @@ var importFromBarrel = createDiagnostic({
2600
2616
  if (!moduleSymbol.exports) return;
2601
2617
  const sourceFile2 = importDeclaration.getSourceFile();
2602
2618
  const nodeForSymbol = element.propertyName || element.name;
2619
+ const aliasSymbol = element.name || element.propertyName;
2620
+ const aliasedName = aliasSymbol.text;
2603
2621
  if (!ts.isIdentifier(nodeForSymbol)) return;
2604
2622
  const importedName = nodeForSymbol.text;
2605
2623
  const reexportedSymbol = moduleSymbol.exports.get(ts.escapeLeadingUnderscores(importedName));
@@ -2622,7 +2640,15 @@ var importFromBarrel = createDiagnostic({
2622
2640
  program
2623
2641
  );
2624
2642
  if (unbarrelledFileName.toLowerCase().indexOf(barrelModuleName.toLowerCase() + "/") === -1) return;
2625
- return { unbarrelledFileName, importedName, barrelModuleName, importClause, namedBindings, importDeclaration };
2643
+ return {
2644
+ unbarrelledFileName,
2645
+ importedName,
2646
+ barrelModuleName,
2647
+ importClause,
2648
+ namedBindings,
2649
+ importDeclaration,
2650
+ aliasedName
2651
+ };
2626
2652
  };
2627
2653
  const nodeToVisit = [];
2628
2654
  const appendNodeToVisit = (node) => {
@@ -2639,14 +2665,21 @@ var importFromBarrel = createDiagnostic({
2639
2665
  }
2640
2666
  const result = isImportedFromBarrelExport(node);
2641
2667
  if (!result) continue;
2642
- const { barrelModuleName, importClause, importDeclaration, importedName, namedBindings, unbarrelledFileName } = result;
2668
+ const {
2669
+ aliasedName,
2670
+ barrelModuleName,
2671
+ importClause,
2672
+ importDeclaration,
2673
+ namedBindings,
2674
+ unbarrelledFileName
2675
+ } = result;
2643
2676
  report({
2644
2677
  node,
2645
2678
  messageText: `Importing from barrel module ${barrelModuleName} is not allowed.`,
2646
2679
  fixes: [
2647
2680
  {
2648
2681
  fixName: "replaceWithUnbarrelledImport",
2649
- description: `Import * as ${importedName} from ${unbarrelledFileName}`,
2682
+ description: `Import * as ${aliasedName} from ${unbarrelledFileName}`,
2650
2683
  apply: gen(function* () {
2651
2684
  const changeTracker = yield* service(ChangeTracker);
2652
2685
  const newImport = ts.factory.createImportDeclaration(
@@ -2654,7 +2687,7 @@ var importFromBarrel = createDiagnostic({
2654
2687
  ts.factory.createImportClause(
2655
2688
  importClause.isTypeOnly || node.isTypeOnly,
2656
2689
  void 0,
2657
- ts.factory.createNamespaceImport(ts.factory.createIdentifier(importedName))
2690
+ ts.factory.createNamespaceImport(ts.factory.createIdentifier(aliasedName))
2658
2691
  ),
2659
2692
  ts.factory.createStringLiteral(unbarrelledFileName)
2660
2693
  );
@@ -2843,18 +2876,39 @@ var missingEffectError = createDiagnostic({
2843
2876
  code: 1,
2844
2877
  severity: "error",
2845
2878
  apply: fn("missingEffectError.apply")(function* (sourceFile, report) {
2879
+ const ts = yield* service(TypeScriptApi);
2846
2880
  const typeChecker = yield* service(TypeCheckerApi);
2847
2881
  const typeParser = yield* service(TypeParser);
2848
2882
  const typeOrder = yield* deterministicTypeOrder;
2883
+ const effectModuleIdentifier = yield* pipe(
2884
+ findImportedModuleIdentifierByPackageAndNameOrBarrel(
2885
+ sourceFile,
2886
+ "effect",
2887
+ "Effect"
2888
+ ),
2889
+ map3((_) => _.text),
2890
+ orElse2(() => succeed("Effect"))
2891
+ );
2892
+ const createDieMessage = (message) => ts.factory.createCallExpression(
2893
+ ts.factory.createPropertyAccessExpression(
2894
+ ts.factory.createIdentifier(effectModuleIdentifier),
2895
+ "dieMessage"
2896
+ ),
2897
+ void 0,
2898
+ [ts.factory.createStringLiteral(message)]
2899
+ );
2849
2900
  const checkForMissingErrorTypes = (node, expectedType, valueNode, realType) => pipe(
2850
2901
  all(
2851
2902
  typeParser.effectType(expectedType, node),
2852
2903
  typeParser.effectType(realType, valueNode)
2853
2904
  ),
2854
2905
  flatMap2(
2855
- ([expectedEffect, realEffect]) => getMissingTypeEntriesInTargetType(
2856
- realEffect.E,
2857
- expectedEffect.E
2906
+ ([expectedEffect, realEffect]) => pipe(
2907
+ getMissingTypeEntriesInTargetType(
2908
+ realEffect.E,
2909
+ expectedEffect.E
2910
+ ),
2911
+ map3((missingErrorTypes) => ({ missingErrorTypes, expectedErrorType: expectedEffect.E }))
2858
2912
  )
2859
2913
  )
2860
2914
  );
@@ -2869,15 +2923,79 @@ var missingEffectError = createDiagnostic({
2869
2923
  valueNode,
2870
2924
  realType
2871
2925
  ),
2872
- map3(
2873
- (missingTypes) => missingTypes.length > 0 ? report(
2926
+ map3((result) => {
2927
+ if (result.missingErrorTypes.length === 0) return;
2928
+ const fixes = [];
2929
+ if (ts.isExpression(valueNode) && result.expectedErrorType.flags & ts.TypeFlags.Never) {
2930
+ fixes.push({
2931
+ fixName: "missingEffectError_catchAll",
2932
+ description: "Catch all errors with Effect.catchAll",
2933
+ apply: gen(function* () {
2934
+ const changeTracker = yield* service(ChangeTracker);
2935
+ changeTracker.insertText(sourceFile, valueNode.getStart(), effectModuleIdentifier + ".catchAll(");
2936
+ changeTracker.insertText(sourceFile, valueNode.getEnd(), ", () => ");
2937
+ changeTracker.insertNodeAt(
2938
+ sourceFile,
2939
+ valueNode.getEnd(),
2940
+ createDieMessage("TODO: catchAll not implemented")
2941
+ );
2942
+ changeTracker.insertText(sourceFile, valueNode.getEnd(), ")");
2943
+ })
2944
+ });
2945
+ }
2946
+ if (ts.isExpression(valueNode)) {
2947
+ const propertyAssignments = pipe(
2948
+ result.missingErrorTypes,
2949
+ map2((_) => typeChecker.getPropertyOfType(_, "_tag")),
2950
+ filter((_) => !!_),
2951
+ map2((_) => typeChecker.getTypeOfSymbolAtLocation(_, valueNode)),
2952
+ filter((_) => !!(_.flags & ts.TypeFlags.Literal)),
2953
+ map2((_) => typeChecker.typeToTypeNode(_, void 0, ts.NodeBuilderFlags.NoTruncation)),
2954
+ filter((_) => !!_ && ts.isLiteralTypeNode(_)),
2955
+ map2((_) => _.literal),
2956
+ filter((_) => ts.isLiteralExpression(_)),
2957
+ map2((_) => _.text),
2958
+ sort(string2),
2959
+ map2(
2960
+ (_) => ts.factory.createPropertyAssignment(
2961
+ ts.factory.createIdentifier(_),
2962
+ ts.factory.createArrowFunction(
2963
+ void 0,
2964
+ void 0,
2965
+ [],
2966
+ void 0,
2967
+ void 0,
2968
+ createDieMessage(`TODO: catchTags() not implemented for ${_}`)
2969
+ )
2970
+ )
2971
+ )
2972
+ );
2973
+ if (propertyAssignments.length === result.missingErrorTypes.length) {
2974
+ fixes.push({
2975
+ fixName: "missingEffectError_tagged",
2976
+ description: "Catch unexpected errors with Effect.catchTag",
2977
+ apply: gen(function* () {
2978
+ const changeTracker = yield* service(ChangeTracker);
2979
+ changeTracker.insertText(sourceFile, valueNode.getStart(), effectModuleIdentifier + ".catchTags(");
2980
+ changeTracker.insertText(sourceFile, valueNode.getEnd(), ", ");
2981
+ changeTracker.insertNodeAt(
2982
+ sourceFile,
2983
+ valueNode.getEnd(),
2984
+ ts.factory.createObjectLiteralExpression(propertyAssignments)
2985
+ );
2986
+ changeTracker.insertText(sourceFile, valueNode.getEnd(), ")");
2987
+ })
2988
+ });
2989
+ }
2990
+ }
2991
+ report(
2874
2992
  {
2875
2993
  node,
2876
- messageText: `Missing '${sortTypes(missingTypes).map((_) => typeChecker.typeToString(_)).join(" | ")}' in the expected Effect errors.`,
2877
- fixes: []
2994
+ messageText: `Missing '${sortTypes(result.missingErrorTypes).map((_) => typeChecker.typeToString(_)).join(" | ")}' in the expected Effect errors.`,
2995
+ fixes
2878
2996
  }
2879
- ) : void 0
2880
- ),
2997
+ );
2998
+ }),
2881
2999
  ignore
2882
3000
  );
2883
3001
  }
@@ -3089,6 +3207,108 @@ Nested Effect-able types may be intended if you plan to later manually flatten o
3089
3207
  })
3090
3208
  });
3091
3209
 
3210
+ // src/diagnostics/scopeInLayerEffect.ts
3211
+ var scopeInLayerEffect = createDiagnostic({
3212
+ name: "scopeInLayerEffect",
3213
+ code: 13,
3214
+ severity: "warning",
3215
+ apply: fn("scopeInLayerEffect.apply")(function* (sourceFile, report) {
3216
+ const ts = yield* service(TypeScriptApi);
3217
+ const typeChecker = yield* service(TypeCheckerApi);
3218
+ const typeParser = yield* service(TypeParser);
3219
+ const layerModuleIdentifier = yield* pipe(
3220
+ findImportedModuleIdentifierByPackageAndNameOrBarrel(
3221
+ sourceFile,
3222
+ "effect",
3223
+ "Layer"
3224
+ ),
3225
+ map3((_) => _.text),
3226
+ orElse2(() => succeed("Layer"))
3227
+ );
3228
+ function parseLayerEffectApiCall(node) {
3229
+ if (!ts.isCallExpression(node)) return;
3230
+ const expression = node.expression;
3231
+ if (!ts.isPropertyAccessExpression(expression)) return;
3232
+ const calledModule = expression.expression;
3233
+ if (!(ts.isIdentifier(calledModule) && calledModule.text === layerModuleIdentifier)) return;
3234
+ const methodIdentifier = expression.name;
3235
+ if (!(ts.isIdentifier(methodIdentifier) && methodIdentifier.text.toLowerCase().startsWith("effect"))) return;
3236
+ return { methodIdentifier };
3237
+ }
3238
+ const reportIfLayerRequireScope = (type, node, methodIdentifier) => {
3239
+ let toCheck = [type];
3240
+ const entries = [];
3241
+ while (toCheck.length > 0) {
3242
+ const type2 = toCheck.pop();
3243
+ if (type2.isUnion()) {
3244
+ toCheck = toCheck.concat(type2.types);
3245
+ } else {
3246
+ entries.push(type2);
3247
+ }
3248
+ }
3249
+ return pipe(
3250
+ firstSuccessOf(entries.map((type2) => typeParser.scopeType(type2, node))),
3251
+ map3(
3252
+ () => report({
3253
+ node,
3254
+ messageText: `Seems like you are constructing a layer with a scope in the requirements.
3255
+ Consider using "scoped" instead to get ride of the scope in the requirements.`,
3256
+ fixes: methodIdentifier ? [{
3257
+ fixName: "scopeInLayerEffect_scoped",
3258
+ description: "Use scoped for Layer creation",
3259
+ apply: gen(function* () {
3260
+ const changeTracker = yield* service(ChangeTracker);
3261
+ changeTracker.replaceNode(
3262
+ sourceFile,
3263
+ methodIdentifier,
3264
+ ts.factory.createIdentifier("scoped")
3265
+ );
3266
+ })
3267
+ }] : []
3268
+ })
3269
+ ),
3270
+ ignore
3271
+ );
3272
+ };
3273
+ const nodeToVisit = [];
3274
+ const appendNodeToVisit = (node) => {
3275
+ nodeToVisit.push(node);
3276
+ return void 0;
3277
+ };
3278
+ ts.forEachChild(sourceFile, appendNodeToVisit);
3279
+ while (nodeToVisit.length > 0) {
3280
+ const node = nodeToVisit.shift();
3281
+ const layerEffectApiCall = parseLayerEffectApiCall(node);
3282
+ if (layerEffectApiCall) {
3283
+ const type = typeChecker.getTypeAtLocation(node);
3284
+ yield* pipe(
3285
+ typeParser.layerType(type, node),
3286
+ flatMap2(({ RIn }) => reportIfLayerRequireScope(RIn, node, layerEffectApiCall.methodIdentifier)),
3287
+ ignore
3288
+ );
3289
+ continue;
3290
+ }
3291
+ if (ts.isClassDeclaration(node) && node.name && node.heritageClauses) {
3292
+ const classSym = typeChecker.getSymbolAtLocation(node.name);
3293
+ if (classSym) {
3294
+ const classType = typeChecker.getTypeOfSymbol(classSym);
3295
+ const defaultLayer = typeChecker.getPropertyOfType(classType, "Default");
3296
+ if (defaultLayer) {
3297
+ const type = typeChecker.getTypeOfSymbolAtLocation(defaultLayer, node);
3298
+ yield* pipe(
3299
+ typeParser.layerType(type, node),
3300
+ flatMap2(({ RIn }) => reportIfLayerRequireScope(RIn, node, void 0)),
3301
+ ignore
3302
+ );
3303
+ continue;
3304
+ }
3305
+ }
3306
+ }
3307
+ ts.forEachChild(node, appendNodeToVisit);
3308
+ }
3309
+ })
3310
+ });
3311
+
3092
3312
  // src/diagnostics/unnecessaryEffectGen.ts
3093
3313
  var unnecessaryEffectGen = createDiagnostic({
3094
3314
  name: "unnecessaryEffectGen",
@@ -3190,7 +3410,8 @@ var diagnostics = [
3190
3410
  unnecessaryPipe,
3191
3411
  genericEffectServices,
3192
3412
  returnEffectInGen,
3193
- importFromBarrel
3413
+ importFromBarrel,
3414
+ scopeInLayerEffect
3194
3415
  ];
3195
3416
 
3196
3417
  // src/transform.ts