@effect/language-service 0.70.0 → 0.71.1

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 CHANGED
@@ -84,6 +84,7 @@ And you're done! You'll now be able to use a set of refactors and diagnostics th
84
84
  - Warn when `Effect.fn` or `Effect.fnUntraced` is used as an IIFE (Immediately Invoked Function Expression), suggesting `Effect.gen` instead
85
85
  - Suggest removing redundant identifier argument when it equals the tag value in `Schema.TaggedClass`, `Schema.TaggedError`, or `Schema.TaggedRequest`
86
86
  - Suggest using `Schema.is` instead of `instanceof` for Effect Schema types
87
+ - Suggest using `Effect.void` instead of `Effect.succeed(undefined)` or `Effect.succeed(void 0)`
87
88
 
88
89
  ### Completions
89
90
 
@@ -140,6 +141,8 @@ Few options can be provided alongside the initialization of the Language Service
140
141
  "diagnosticsName": true, // controls whether to include the rule name in diagnostic messages (default: true)
141
142
  "missingDiagnosticNextLine": "warning", // controls the severity of warnings for unused @effect-diagnostics-next-line comments (default: "warning", allowed values: off,error,warning,message,suggestion)
142
143
  "includeSuggestionsInTsc": true, // when enabled with effect-language-service patch enabled, diagnostics with "suggestion" severity will be reported as "message" in TSC with "[suggestion]" prefix; useful to help steer LLM output (default: true)
144
+ "ignoreEffectWarningsInTscExitCode": false, // if set to true, effect-related warnings won't change the exit code of tsc, meaning that tsc will compile fine even if effect warnings are emitted (default: false)
145
+ "ignoreEffectSuggestionsInTscExitCode": true, // if set to true, effect-related suggestions won't change the exit code of tsc (default: true)
143
146
  "quickinfo": true, // controls Effect quickinfo (default: true)
144
147
  "quickinfoEffectParameters": "whenTruncated", // (default: "whenTruncated") controls when to display effect type parameters always,never,whenTruncated
145
148
  "quickinfoMaximumLength": -1, // controls how long can be the types in the quickinfo hover (helps with very long type to improve perfs, defaults to -1 for no truncation, can be any number eg. 1000 and TS will try to fit as much as possible in that budget, higher number means more info.)
package/cli.js CHANGED
@@ -30214,7 +30214,7 @@ var runMain3 = runMain2;
30214
30214
  // package.json
30215
30215
  var package_default = {
30216
30216
  name: "@effect/language-service",
30217
- version: "0.70.0",
30217
+ version: "0.71.1",
30218
30218
  packageManager: "pnpm@8.11.0",
30219
30219
  publishConfig: {
30220
30220
  access: "public",
@@ -32038,6 +32038,8 @@ var defaults = {
32038
32038
  skipLeadingPath: ["src/"]
32039
32039
  }],
32040
32040
  extendedKeyDetection: false,
32041
+ ignoreEffectWarningsInTscExitCode: false,
32042
+ ignoreEffectSuggestionsInTscExitCode: true,
32041
32043
  pipeableMinArgCount: 2,
32042
32044
  effectFn: ["span"],
32043
32045
  layerGraphFollowDepth: 0,
@@ -32063,6 +32065,8 @@ function parse4(config2) {
32063
32065
  diagnosticsName: isObject(config2) && hasProperty(config2, "diagnosticsName") && isBoolean(config2.diagnosticsName) ? config2.diagnosticsName : defaults.diagnosticsName,
32064
32066
  missingDiagnosticNextLine: isObject(config2) && hasProperty(config2, "missingDiagnosticNextLine") && isString(config2.missingDiagnosticNextLine) && isValidSeverityLevel(config2.missingDiagnosticNextLine) ? config2.missingDiagnosticNextLine : defaults.missingDiagnosticNextLine,
32065
32067
  includeSuggestionsInTsc: isObject(config2) && hasProperty(config2, "includeSuggestionsInTsc") && isBoolean(config2.includeSuggestionsInTsc) ? config2.includeSuggestionsInTsc : defaults.includeSuggestionsInTsc,
32068
+ ignoreEffectWarningsInTscExitCode: isObject(config2) && hasProperty(config2, "ignoreEffectWarningsInTscExitCode") && isBoolean(config2.ignoreEffectWarningsInTscExitCode) ? config2.ignoreEffectWarningsInTscExitCode : defaults.ignoreEffectWarningsInTscExitCode,
32069
+ ignoreEffectSuggestionsInTscExitCode: isObject(config2) && hasProperty(config2, "ignoreEffectSuggestionsInTscExitCode") && isBoolean(config2.ignoreEffectSuggestionsInTscExitCode) ? config2.ignoreEffectSuggestionsInTscExitCode : defaults.ignoreEffectSuggestionsInTscExitCode,
32066
32070
  quickinfo: isObject(config2) && hasProperty(config2, "quickinfo") && isBoolean(config2.quickinfo) ? config2.quickinfo : defaults.quickinfo,
32067
32071
  quickinfoEffectParameters: isObject(config2) && hasProperty(config2, "quickinfoEffectParameters") && isString(config2.quickinfoEffectParameters) && ["always", "never", "whentruncated"].includes(config2.quickinfoEffectParameters.toLowerCase()) ? config2.quickinfoEffectParameters.toLowerCase() : defaults.quickinfoEffectParameters,
32068
32072
  quickinfoMaximumLength: isObject(config2) && hasProperty(config2, "quickinfoMaximumLength") && isNumber(config2.quickinfoMaximumLength) ? config2.quickinfoMaximumLength : defaults.quickinfoMaximumLength,
@@ -36899,6 +36903,57 @@ var effectMapVoid = createDiagnostic({
36899
36903
  })
36900
36904
  });
36901
36905
 
36906
+ // src/diagnostics/effectSucceedWithVoid.ts
36907
+ var effectSucceedWithVoid = createDiagnostic({
36908
+ name: "effectSucceedWithVoid",
36909
+ code: 47,
36910
+ description: "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
36911
+ severity: "suggestion",
36912
+ apply: fn2("effectSucceedWithVoid.apply")(function* (sourceFile, report) {
36913
+ const ts = yield* service2(TypeScriptApi);
36914
+ const typeParser = yield* service2(TypeParser);
36915
+ const tsUtils = yield* service2(TypeScriptUtils);
36916
+ const nodeToVisit = [];
36917
+ const appendNodeToVisit = (node) => {
36918
+ nodeToVisit.push(node);
36919
+ return void 0;
36920
+ };
36921
+ ts.forEachChild(sourceFile, appendNodeToVisit);
36922
+ while (nodeToVisit.length > 0) {
36923
+ const node = nodeToVisit.shift();
36924
+ ts.forEachChild(node, appendNodeToVisit);
36925
+ if (ts.isCallExpression(node)) {
36926
+ const isSucceedCall = yield* pipe(
36927
+ typeParser.isNodeReferenceToEffectModuleApi("succeed")(node.expression),
36928
+ option5
36929
+ );
36930
+ if (isSome2(isSucceedCall)) {
36931
+ const argument = node.arguments[0];
36932
+ if (!argument) continue;
36933
+ if (!tsUtils.isVoidExpression(argument)) continue;
36934
+ report({
36935
+ location: node,
36936
+ messageText: "Effect.void can be used instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
36937
+ fixes: [{
36938
+ fixName: "effectSucceedWithVoid_fix",
36939
+ description: "Replace with Effect.void",
36940
+ apply: gen3(function* () {
36941
+ const changeTracker = yield* service2(ChangeTracker);
36942
+ const effectModuleIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(sourceFile, "effect", "Effect") || "Effect";
36943
+ const newNode = ts.factory.createPropertyAccessExpression(
36944
+ ts.factory.createIdentifier(effectModuleIdentifier),
36945
+ ts.factory.createIdentifier("void")
36946
+ );
36947
+ changeTracker.replaceNode(sourceFile, node, newNode);
36948
+ })
36949
+ }]
36950
+ });
36951
+ }
36952
+ }
36953
+ }
36954
+ })
36955
+ });
36956
+
36902
36957
  // src/diagnostics/floatingEffect.ts
36903
36958
  var floatingEffect = createDiagnostic({
36904
36959
  name: "floatingEffect",
@@ -37042,8 +37097,7 @@ var globalErrorInEffectCatch = createDiagnostic({
37042
37097
  );
37043
37098
  report({
37044
37099
  location: node.expression,
37045
- messageText: `The 'catch' callback in ${nodeText} returns the global Error type. It's not recommended to use the global Error type in Effect failures as they can get merged together.
37046
- Instead, use tagged errors or custom errors with a discriminator property to get properly type-checked errors.`,
37100
+ messageText: `The 'catch' callback in ${nodeText} returns global 'Error', which loses type safety as untagged errors merge together. Consider using a tagged error and optionally wrapping the original in a 'cause' property.`,
37047
37101
  fixes: []
37048
37102
  });
37049
37103
  }
@@ -37095,7 +37149,7 @@ var globalErrorInEffectFailure = createDiagnostic({
37095
37149
  if (hasGlobalError) {
37096
37150
  report({
37097
37151
  location: node,
37098
- messageText: `The global Error type is used in an Effect failure channel. It's not recommended to use the global Error type in Effect failures as they can get merged together. Instead, use tagged errors or custom errors with a discriminator property to get properly type-checked errors.`,
37152
+ messageText: `Global 'Error' loses type safety as untagged errors merge together in the Effect failure channel. Consider using a tagged error and optionally wrapping the original in a 'cause' property.`,
37099
37153
  fixes: []
37100
37154
  });
37101
37155
  }
@@ -39769,6 +39823,7 @@ var diagnostics = [
39769
39823
  globalErrorInEffectFailure,
39770
39824
  layerMergeAllWithDependencies,
39771
39825
  effectMapVoid,
39826
+ effectSucceedWithVoid,
39772
39827
  effectFnIife,
39773
39828
  effectFnOpportunity,
39774
39829
  redundantSchemaTagIdentifier,
@@ -41455,6 +41510,8 @@ var getPatchesForModule = fn("getPatchesForModule")(
41455
41510
  let insertCheckSourceFilePosition = none2();
41456
41511
  let insertSkipPrecedingCommentDirectivePosition = none2();
41457
41512
  let insertAppendMetadataRelationErrorPosition = none2();
41513
+ let insertExtractDiagnosticsForExitStatusPosition = none2();
41514
+ let replacedBindingExtractDiagnosticsForExitStatus = none2();
41458
41515
  let nodesToCheck = [];
41459
41516
  function findNodeAtPositionIncludingTrivia(sourceFile2, position) {
41460
41517
  function find3(node) {
@@ -41483,6 +41540,7 @@ var getPatchesForModule = fn("getPatchesForModule")(
41483
41540
  if (!pushFunctionDeclarationNode("checkSourceFileWorker")) requiresFullScan = true;
41484
41541
  if (!pushFunctionDeclarationNode("markPrecedingCommentDirectiveLine")) requiresFullScan = true;
41485
41542
  if (!pushFunctionDeclarationNode("reportRelationError")) requiresFullScan = true;
41543
+ if (!pushFunctionDeclarationNode("emitFilesAndReportErrorsAndGetExitStatus")) requiresFullScan = true;
41486
41544
  if (requiresFullScan) nodesToCheck = [sourceFile];
41487
41545
  while (nodesToCheck.length > 0) {
41488
41546
  const node = nodesToCheck.shift();
@@ -41517,6 +41575,29 @@ var getPatchesForModule = fn("getPatchesForModule")(
41517
41575
  });
41518
41576
  }
41519
41577
  }
41578
+ } else if (ts.isCallExpression(node)) {
41579
+ const callee = node.expression;
41580
+ if (ts.isIdentifier(callee) && ts.idText(callee) === "emitFilesAndReportErrors") {
41581
+ const parentVariableDeclaration = ts.findAncestor(node, ts.isVariableDeclaration);
41582
+ if (parentVariableDeclaration) {
41583
+ const parentVariableStatement = ts.findAncestor(parentVariableDeclaration, ts.isVariableStatement);
41584
+ if (parentVariableStatement) {
41585
+ const parentBlock = parentVariableStatement.parent;
41586
+ if (ts.isBlock(parentBlock)) {
41587
+ const parentFunctionDeclaration = parentBlock.parent;
41588
+ if (ts.isFunctionDeclaration(parentFunctionDeclaration) && parentFunctionDeclaration.name && ts.isIdentifier(parentFunctionDeclaration.name) && ts.idText(parentFunctionDeclaration.name) === "emitFilesAndReportErrorsAndGetExitStatus") {
41589
+ insertExtractDiagnosticsForExitStatusPosition = some2({
41590
+ position: parentVariableStatement.end
41591
+ });
41592
+ replacedBindingExtractDiagnosticsForExitStatus = some2({
41593
+ pos: parentVariableDeclaration.name.pos,
41594
+ end: parentVariableDeclaration.name.end
41595
+ });
41596
+ }
41597
+ }
41598
+ }
41599
+ }
41600
+ }
41520
41601
  }
41521
41602
  ts.forEachChild(node, (child) => {
41522
41603
  nodesToCheck.push(child);
@@ -41571,9 +41652,7 @@ var getPatchesForModule = fn("getPatchesForModule")(
41571
41652
  )
41572
41653
  );
41573
41654
  if (isNone2(insertSkipPrecedingCommentDirectivePosition)) {
41574
- return yield* fail7(
41575
- new UnableToFindPositionToPatchError({ positionToFind: "skip preceding comment directive" })
41576
- );
41655
+ return yield* new UnableToFindPositionToPatchError({ positionToFind: "skip preceding comment directive" });
41577
41656
  }
41578
41657
  patches.push(
41579
41658
  yield* makeEffectLspPatchChange(
@@ -41585,6 +41664,33 @@ var getPatchesForModule = fn("getPatchesForModule")(
41585
41664
  version
41586
41665
  )
41587
41666
  );
41667
+ if (isNone2(insertExtractDiagnosticsForExitStatusPosition)) {
41668
+ return yield* new UnableToFindPositionToPatchError({ positionToFind: "extractDiagnosticsForExitStatus" });
41669
+ }
41670
+ if (isNone2(replacedBindingExtractDiagnosticsForExitStatus)) {
41671
+ return yield* new UnableToFindPositionToPatchError({ positionToFind: "extractDiagnosticsForExitStatus-binding" });
41672
+ }
41673
+ patches.push(
41674
+ yield* makeEffectLspPatchChange(
41675
+ sourceFile.text,
41676
+ replacedBindingExtractDiagnosticsForExitStatus.value.pos,
41677
+ replacedBindingExtractDiagnosticsForExitStatus.value.end,
41678
+ ` { emitResult, diagnostics: tscDiagnostics }`,
41679
+ " ",
41680
+ version
41681
+ )
41682
+ );
41683
+ patches.push(
41684
+ yield* makeEffectLspPatchChange(
41685
+ sourceFile.text,
41686
+ insertExtractDiagnosticsForExitStatusPosition.value.position,
41687
+ insertExtractDiagnosticsForExitStatusPosition.value.position,
41688
+ `const diagnostics = effectLspPatchUtils().extractDiagnosticsForExitStatus(${moduleName === "typescript" ? "module.exports" : "effectLspTypeScriptApis()"}, program, tscDiagnostics, "${moduleName}")
41689
+ `,
41690
+ "\n",
41691
+ version
41692
+ )
41693
+ );
41588
41694
  let eofPos = sourceFile.text.lastIndexOf("// src/") - 1;
41589
41695
  if (eofPos < 0) eofPos = sourceFile.text.length;
41590
41696
  if (moduleName !== "typescript") {