From 8702c27c9b14a8a70b3fd86c69c038abaf2c4ae0 Mon Sep 17 00:00:00 2001 From: Mattia Manzati Date: Fri, 6 Mar 2026 19:33:06 +0100 Subject: [PATCH 1/7] Add serviceNotAsClass diagnostic --- .changeset/service-not-as-class-diagnostic.md | 7 ++ README.md | 1 + .../__snapshots__/completions.test.ts.snap | 4 +- .../serviceNotAsClass.ts.codefixes | 6 + .../diagnostics/serviceNotAsClass.ts.output | 5 + ...s.ts.serviceNotAsClass.from180to223.output | 18 +++ ...s.ts.serviceNotAsClass.from348to413.output | 18 +++ .../examples/diagnostics/serviceNotAsClass.ts | 17 +++ packages/language-service/src/diagnostics.ts | 4 +- .../src/diagnostics/serviceNotAsClass.ts | 111 ++++++++++++++++++ 10 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 .changeset/service-not-as-class-diagnostic.md create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.codefixes create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.serviceNotAsClass.from180to223.output create mode 100644 packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.serviceNotAsClass.from348to413.output create mode 100644 packages/harness-effect-v4/examples/diagnostics/serviceNotAsClass.ts create mode 100644 packages/language-service/src/diagnostics/serviceNotAsClass.ts diff --git a/.changeset/service-not-as-class-diagnostic.md b/.changeset/service-not-as-class-diagnostic.md new file mode 100644 index 00000000..80aa0308 --- /dev/null +++ b/.changeset/service-not-as-class-diagnostic.md @@ -0,0 +1,7 @@ +--- +"@effect/language-service": minor +--- + +Add the `serviceNotAsClass` diagnostic to warn when `ServiceMap.Service` is used as a variable assignment instead of in a class declaration. + +Includes an auto-fix that converts `const Config = ServiceMap.Service("Config")` to `class Config extends ServiceMap.Service()("Config") {}`. diff --git a/README.md b/README.md index 78756040..313c0bf6 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ And you're done! You'll now be able to use a set of refactors and diagnostics th - Suggest using `Schema.is` instead of `instanceof` for Effect Schema types - Suggest using `Effect.void` instead of `Effect.succeed(undefined)` or `Effect.succeed(void 0)` - Warn when using outdated Effect v3 APIs in an Effect v4 project, with guidance on the correct v4 replacement (renamed, changed, or removed APIs) +- Warn when `ServiceMap.Service` is used as a variable instead of a class declaration ### Completions diff --git a/packages/harness-effect-v4/__snapshots__/completions.test.ts.snap b/packages/harness-effect-v4/__snapshots__/completions.test.ts.snap index e4224a7c..372fc49a 100644 --- a/packages/harness-effect-v4/__snapshots__/completions.test.ts.snap +++ b/packages/harness-effect-v4/__snapshots__/completions.test.ts.snap @@ -143,7 +143,7 @@ exports[`Completion effectDataClasses > effectDataClasses.ts at 4:35 1`] = ` exports[`Completion effectDiagnosticsComment > effectDiagnosticsComment.ts at 2:5 1`] = ` [ { - "insertText": "@effect-diagnostics \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", + "insertText": "@effect-diagnostics \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", "isSnippet": true, "kind": "string", "name": "@effect-diagnostics", @@ -154,7 +154,7 @@ exports[`Completion effectDiagnosticsComment > effectDiagnosticsComment.ts at 2: "sortText": "11", }, { - "insertText": "@effect-diagnostics-next-line \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", + "insertText": "@effect-diagnostics-next-line \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", "isSnippet": true, "kind": "string", "name": "@effect-diagnostics-next-line", diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.codefixes b/packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.codefixes new file mode 100644 index 00000000..57319b20 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.codefixes @@ -0,0 +1,6 @@ +serviceNotAsClass from 180 to 223 +serviceNotAsClass_skipNextLine from 180 to 223 +serviceNotAsClass_skipFile from 180 to 223 +serviceNotAsClass from 348 to 413 +serviceNotAsClass_skipNextLine from 348 to 413 +serviceNotAsClass_skipFile from 348 to 413 \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.output b/packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.output new file mode 100644 index 00000000..24f73efc --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.output @@ -0,0 +1,5 @@ +ServiceMap.Service("Config") +7:15 - 7:58 | 0 | ServiceMap.Service should be used in a class declaration instead of as a variable. Use: class Config extends ServiceMap.Service()("Config") {} effect(serviceNotAsClass) + +ServiceMap.Service("@my-app/ArtifactStore") +11:29 - 11:94 | 0 | ServiceMap.Service should be used in a class declaration instead of as a variable. Use: class ArtifactStore extends ServiceMap.Service()("@my-app/ArtifactStore") {} effect(serviceNotAsClass) \ No newline at end of file diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.serviceNotAsClass.from180to223.output b/packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.serviceNotAsClass.from180to223.output new file mode 100644 index 00000000..961f5c32 --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.serviceNotAsClass.from180to223.output @@ -0,0 +1,18 @@ +// code fix serviceNotAsClass output for range 180 - 223 +// @effect-diagnostics serviceNotAsClass:warning +import { ServiceMap } from "effect" + +interface ConfigService {} + +// Flagged: const variable with ServiceMap.Service +class Config extends ServiceMap.Service()("Config") { } + +// Flagged: exported const variable with ServiceMap.Service +interface ArtifactStoreService {} +export const ArtifactStore = ServiceMap.Service("@my-app/ArtifactStore") + +// Not flagged: correct class extends form +class MyService extends ServiceMap.Service()("MyService") {} + +// Not flagged: non-ServiceMap.Service const +const x = 42 diff --git a/packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.serviceNotAsClass.from348to413.output b/packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.serviceNotAsClass.from348to413.output new file mode 100644 index 00000000..deff9eee --- /dev/null +++ b/packages/harness-effect-v4/__snapshots__/diagnostics/serviceNotAsClass.ts.serviceNotAsClass.from348to413.output @@ -0,0 +1,18 @@ +// code fix serviceNotAsClass output for range 348 - 413 +// @effect-diagnostics serviceNotAsClass:warning +import { ServiceMap } from "effect" + +interface ConfigService {} + +// Flagged: const variable with ServiceMap.Service +const Config = ServiceMap.Service("Config") + +// Flagged: exported const variable with ServiceMap.Service +interface ArtifactStoreService {} +export class ArtifactStore extends ServiceMap.Service()("@my-app/ArtifactStore") { } + +// Not flagged: correct class extends form +class MyService extends ServiceMap.Service()("MyService") {} + +// Not flagged: non-ServiceMap.Service const +const x = 42 diff --git a/packages/harness-effect-v4/examples/diagnostics/serviceNotAsClass.ts b/packages/harness-effect-v4/examples/diagnostics/serviceNotAsClass.ts new file mode 100644 index 00000000..08a05cc9 --- /dev/null +++ b/packages/harness-effect-v4/examples/diagnostics/serviceNotAsClass.ts @@ -0,0 +1,17 @@ +// @effect-diagnostics serviceNotAsClass:warning +import { ServiceMap } from "effect" + +interface ConfigService {} + +// Flagged: const variable with ServiceMap.Service +const Config = ServiceMap.Service("Config") + +// Flagged: exported const variable with ServiceMap.Service +interface ArtifactStoreService {} +export const ArtifactStore = ServiceMap.Service("@my-app/ArtifactStore") + +// Not flagged: correct class extends form +class MyService extends ServiceMap.Service()("MyService") {} + +// Not flagged: non-ServiceMap.Service const +const x = 42 diff --git a/packages/language-service/src/diagnostics.ts b/packages/language-service/src/diagnostics.ts index 1f1cc51c..af03d975 100644 --- a/packages/language-service/src/diagnostics.ts +++ b/packages/language-service/src/diagnostics.ts @@ -40,6 +40,7 @@ import { schemaStructWithTag } from "./diagnostics/schemaStructWithTag.js" import { schemaSyncInEffect } from "./diagnostics/schemaSyncInEffect.js" import { schemaUnionOfLiterals } from "./diagnostics/schemaUnionOfLiterals.js" import { scopeInLayerEffect } from "./diagnostics/scopeInLayerEffect.js" +import { serviceNotAsClass } from "./diagnostics/serviceNotAsClass.js" import { strictBooleanExpressions } from "./diagnostics/strictBooleanExpressions.js" import { strictEffectProvide } from "./diagnostics/strictEffectProvide.js" import { tryCatchInEffectGen } from "./diagnostics/tryCatchInEffectGen.js" @@ -101,5 +102,6 @@ export const diagnostics = [ redundantSchemaTagIdentifier, schemaSyncInEffect, preferSchemaOverJson, - extendsNativeError + extendsNativeError, + serviceNotAsClass ] diff --git a/packages/language-service/src/diagnostics/serviceNotAsClass.ts b/packages/language-service/src/diagnostics/serviceNotAsClass.ts new file mode 100644 index 00000000..009a1b31 --- /dev/null +++ b/packages/language-service/src/diagnostics/serviceNotAsClass.ts @@ -0,0 +1,111 @@ +import { pipe } from "effect" +import type ts from "typescript" +import * as LSP from "../core/LSP.js" +import * as Nano from "../core/Nano.js" +import * as TypeParser from "../core/TypeParser.js" +import * as TypeScriptApi from "../core/TypeScriptApi.js" + +export const serviceNotAsClass = LSP.createDiagnostic({ + name: "serviceNotAsClass", + code: 51, + description: "Warns when ServiceMap.Service is used as a variable instead of a class declaration", + severity: "off", + apply: Nano.fn("serviceNotAsClass.apply")(function*(sourceFile, report) { + const ts = yield* Nano.service(TypeScriptApi.TypeScriptApi) + const typeParser = yield* Nano.service(TypeParser.TypeParser) + + if (typeParser.supportedEffect() === "v3") return + + const nodeToVisit: Array = [] + const appendNodeToVisit = (node: ts.Node) => { + nodeToVisit.push(node) + return undefined + } + ts.forEachChild(sourceFile, appendNodeToVisit) + + while (nodeToVisit.length > 0) { + const node = nodeToVisit.shift()! + ts.forEachChild(node, appendNodeToVisit) + + if (!ts.isVariableDeclaration(node)) continue + if (!node.initializer || !ts.isCallExpression(node.initializer)) continue + + const callExpr = node.initializer + if (!callExpr.typeArguments || callExpr.typeArguments.length === 0) continue + const typeArgs = callExpr.typeArguments + + // Check parent VariableDeclarationList uses const + const declList = node.parent + if (!ts.isVariableDeclarationList(declList)) continue + if (!(declList.flags & ts.NodeFlags.Const)) continue + + const isServiceMapService = yield* pipe( + typeParser.isNodeReferenceToServiceMapModuleApi("Service")(callExpr.expression), + Nano.orUndefined + ) + if (!isServiceMapService) continue + + const variableName = ts.isIdentifier(node.name) ? ts.idText(node.name) : node.name.getText(sourceFile) + const variableStatement = declList.parent + + const argsText = callExpr.arguments.length > 0 + ? callExpr.arguments.map((a) => sourceFile.text.substring(a.pos, a.end)).join(", ") + : "" + + const shapeText = typeArgs.length > 0 + ? typeArgs.map((t) => t.getText(sourceFile)).join(", ") + : "Shape" + + report({ + location: callExpr, + messageText: + `ServiceMap.Service should be used in a class declaration instead of as a variable. Use: class ${variableName} extends ServiceMap.Service<${variableName}, ${shapeText}>()("${ + argsText.replace(/['"]/g, "") + }") {}`, + fixes: [{ + fixName: "serviceNotAsClass", + description: `Convert to class declaration`, + apply: Nano.gen(function*() { + const changeTracker = yield* Nano.service(TypeScriptApi.ChangeTracker) + const targetNode = ts.isVariableStatement(variableStatement) ? variableStatement : declList + + // Build inner call: ServiceMap.Service() + const innerCall = ts.factory.createCallExpression( + callExpr.expression, + [ts.factory.createTypeReferenceNode(variableName), ...typeArgs], + [] + ) + + // Build outer call: ServiceMap.Service()(args...) + const outerCall = ts.factory.createCallExpression( + innerCall, + undefined, + [...callExpr.arguments] + ) + + // Build heritage clause: extends ServiceMap.Service()(args...) + const heritageClause = ts.factory.createHeritageClause( + ts.SyntaxKind.ExtendsKeyword, + [ts.factory.createExpressionWithTypeArguments(outerCall, undefined)] + ) + + // Build class declaration — reuse existing modifiers from the variable statement + const modifiers = ts.isVariableStatement(variableStatement) + ? variableStatement.modifiers + : undefined + + const classDeclaration = ts.factory.createClassDeclaration( + modifiers, + ts.isIdentifier(node.name) ? node.name : ts.factory.createIdentifier(variableName), + undefined, + [heritageClause], + [] + ) + + changeTracker.replaceNode(sourceFile, targetNode, classDeclaration) + }) + }] + }) + } + }) +}) From 5d5e9ca6101fc8644b992f4659f732e838735b80 Mon Sep 17 00:00:00 2001 From: Mattia Manzati Date: Fri, 6 Mar 2026 19:54:11 +0100 Subject: [PATCH 2/7] Replace deprecated getText usage --- packages/language-service/src/core/TypeScriptApi.ts | 5 +++++ .../language-service/src/diagnostics/serviceNotAsClass.ts | 8 +++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/language-service/src/core/TypeScriptApi.ts b/packages/language-service/src/core/TypeScriptApi.ts index b9ca1e41..8c0cacc6 100644 --- a/packages/language-service/src/core/TypeScriptApi.ts +++ b/packages/language-service/src/core/TypeScriptApi.ts @@ -179,6 +179,11 @@ declare module "typescript" { getTypeParameterAtPosition(pos: number): ts.Type } + export interface Node { + /** @deprecated Use sourceFile.text.substring(node.pos, node.end) instead */ + getText(sourceFile?: ts.SourceFile): string + } + export interface Type { /** @deprecated Use typeChecker.getSignaturesOfType(type, ts.SignatureKind.Construct) instead */ getConstructSignatures(): ReadonlyArray diff --git a/packages/language-service/src/diagnostics/serviceNotAsClass.ts b/packages/language-service/src/diagnostics/serviceNotAsClass.ts index 009a1b31..05e30a80 100644 --- a/packages/language-service/src/diagnostics/serviceNotAsClass.ts +++ b/packages/language-service/src/diagnostics/serviceNotAsClass.ts @@ -45,15 +45,17 @@ export const serviceNotAsClass = LSP.createDiagnostic({ ) if (!isServiceMapService) continue - const variableName = ts.isIdentifier(node.name) ? ts.idText(node.name) : node.name.getText(sourceFile) + const variableName = ts.isIdentifier(node.name) + ? ts.idText(node.name) + : sourceFile.text.substring(node.name.getStart(sourceFile), node.name.end) const variableStatement = declList.parent const argsText = callExpr.arguments.length > 0 - ? callExpr.arguments.map((a) => sourceFile.text.substring(a.pos, a.end)).join(", ") + ? callExpr.arguments.map((a) => sourceFile.text.substring(a.getStart(sourceFile), a.end)).join(", ") : "" const shapeText = typeArgs.length > 0 - ? typeArgs.map((t) => t.getText(sourceFile)).join(", ") + ? typeArgs.map((t) => sourceFile.text.substring(t.getStart(sourceFile), t.end)).join(", ") : "Shape" report({ From ede25ba1a4cd788ee3156a8ab3c509d3c6b31ca6 Mon Sep 17 00:00:00 2001 From: Mattia Manzati Date: Fri, 6 Mar 2026 19:55:26 +0100 Subject: [PATCH 3/7] Replace remaining getText usage --- .../language-service/src/cli/layerinfo.ts | 2 +- .../src/core/TypeScriptApi.ts | 5 ---- .../language-service/src/utils/SchemaGen.ts | 25 +++++++++++++++---- .../test/piping-flows.test.ts | 14 ++++++++--- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/packages/language-service/src/cli/layerinfo.ts b/packages/language-service/src/cli/layerinfo.ts index cfad6fdc..26a52263 100644 --- a/packages/language-service/src/cli/layerinfo.ts +++ b/packages/language-service/src/cli/layerinfo.ts @@ -319,7 +319,7 @@ function getExpressionName(tsApi: TypeScriptApi.TypeScriptApi, expr: ts.Expressi return getExpressionName(tsApi, expr.expression) } // Fallback: truncate the text representation - const text = expr.getText().replace(/\s+/g, " ") + const text = expr.getSourceFile().text.substring(expr.getStart(), expr.end).replace(/\s+/g, " ") return text.length > 30 ? text.slice(0, 27) + "..." : text } diff --git a/packages/language-service/src/core/TypeScriptApi.ts b/packages/language-service/src/core/TypeScriptApi.ts index 8c0cacc6..b9ca1e41 100644 --- a/packages/language-service/src/core/TypeScriptApi.ts +++ b/packages/language-service/src/core/TypeScriptApi.ts @@ -179,11 +179,6 @@ declare module "typescript" { getTypeParameterAtPosition(pos: number): ts.Type } - export interface Node { - /** @deprecated Use sourceFile.text.substring(node.pos, node.end) instead */ - getText(sourceFile?: ts.SourceFile): string - } - export interface Type { /** @deprecated Use typeChecker.getSignaturesOfType(type, ts.SignatureKind.Construct) instead */ getConstructSignatures(): ReadonlyArray diff --git a/packages/language-service/src/utils/SchemaGen.ts b/packages/language-service/src/utils/SchemaGen.ts index 77264510..7b426d0c 100644 --- a/packages/language-service/src/utils/SchemaGen.ts +++ b/packages/language-service/src/utils/SchemaGen.ts @@ -29,7 +29,10 @@ export class OnlyLiteralPropertiesSupportedError { } toString() { - return `Could not process ${this.node.getText()} as only literal properties are supported.` + const sourceFile = this.node.getSourceFile() + return `Could not process ${ + sourceFile.text.substring(this.node.getStart(sourceFile), this.node.end) + } as only literal properties are supported.` } } @@ -40,7 +43,10 @@ export class RequiredExplicitTypesError { ) { } toString() { - return `Could not process ${this.node.getText()} as only explicit types are supported.` + const sourceFile = this.node.getSourceFile() + return `Could not process ${ + sourceFile.text.substring(this.node.getStart(sourceFile), this.node.end) + } as only explicit types are supported.` } } @@ -51,7 +57,10 @@ export class IndexSignatureWithMoreThanOneParameterError { ) { } toString() { - return `Could not process ${this.node.getText()} as only index signatures with one parameter are supported.` + const sourceFile = this.node.getSourceFile() + return `Could not process ${ + sourceFile.text.substring(this.node.getStart(sourceFile), this.node.end) + } as only index signatures with one parameter are supported.` } } @@ -186,7 +195,7 @@ const createUnsupportedNodeComment = ( ts.addSyntheticTrailingComment( ts.factory.createIdentifier(""), ts.SyntaxKind.MultiLineCommentTrivia, - " Not supported conversion: " + node.getText(sourceFile) + " " + " Not supported conversion: " + sourceFile.text.substring(node.getStart(sourceFile), node.end) + " " ) export const processNode: ( @@ -302,7 +311,13 @@ export const processNode: ( ts.isIndexedAccessTypeNode(node) && ts.isParenthesizedTypeNode(node.objectType) && ts.isTypeQueryNode(node.objectType.type) && ts.isTypeOperatorNode(node.indexType) && node.indexType.operator === ts.SyntaxKind.KeyOfKeyword && ts.isTypeQueryNode(node.indexType.type) && - node.indexType.type.exprName.getText().trim() === node.objectType.type.exprName.getText().trim() + sourceFile.text.substring( + node.indexType.type.exprName.getStart(sourceFile), + node.indexType.type.exprName.end + ).trim() === sourceFile.text.substring( + node.objectType.type.exprName.getStart(sourceFile), + node.objectType.type.exprName.end + ).trim() ) { const typeChecker = yield* Nano.service(TypeCheckerApi.TypeCheckerApi) const typeCheckerUtils = yield* Nano.service(TypeCheckerUtils.TypeCheckerUtils) diff --git a/packages/language-service/test/piping-flows.test.ts b/packages/language-service/test/piping-flows.test.ts index bb9894e2..16134d17 100644 --- a/packages/language-service/test/piping-flows.test.ts +++ b/packages/language-service/test/piping-flows.test.ts @@ -38,18 +38,24 @@ function formatPipingFlow( lines.push(`=== Piping Flow ===`) lines.push(`Location: ${start.line + 1}:${start.character + 1} - ${end.line + 1}:${end.character + 1}`) - lines.push(`Node: ${flow.node.getText().replace(/\n/g, "\\n")}`) + lines.push(`Node: ${ + sourceFile.text.substring(flow.node.getStart(sourceFile), flow.node.end).replace(/\n/g, "\\n") + }`) lines.push(`Node Kind: ${ts.SyntaxKind[flow.node.kind]}`) lines.push(``) - lines.push(`Subject: ${flow.subject.node.getText().replace(/\n/g, "\\n")}`) + lines.push(`Subject: ${ + sourceFile.text.substring(flow.subject.node.getStart(sourceFile), flow.subject.node.end).replace(/\n/g, "\\n") + }`) lines.push(`Subject Type: ${flow.subject.outType ? typeChecker.typeToString(flow.subject.outType) : "unknown"}`) lines.push(``) lines.push(`Transformations (${flow.transformations.length}):`) for (let i = 0; i < flow.transformations.length; i++) { const t = flow.transformations[i] - const calleeText = t.callee.getText() - const argsText = t.args ? t.args.map((a) => a.getText().replace(/\n/g, "\\n")).join(", ") : undefined + const calleeText = sourceFile.text.substring(t.callee.getStart(sourceFile), t.callee.end) + const argsText = t.args + ? t.args.map((a) => sourceFile.text.substring(a.getStart(sourceFile), a.end).replace(/\n/g, "\\n")).join(", ") + : undefined const typeText = t.outType ? typeChecker.typeToString(t.outType) : "unknown" lines.push(` [${i}] kind: ${t.kind}`) From 443bcd09570324efec143251a4662b886c0b54d3 Mon Sep 17 00:00:00 2001 From: Mattia Manzati Date: Fri, 6 Mar 2026 20:15:33 +0100 Subject: [PATCH 4/7] Replace getStart usage in language-service --- packages/language-service/src/cli/layerinfo.ts | 3 ++- .../src/cli/utils/ExportedSymbols.ts | 5 ++++- .../src/diagnostics/serviceNotAsClass.ts | 6 +++--- .../language-service/src/goto/effectRpcDefinition.ts | 10 ++++++++-- .../language-service/src/inlays/middlewareGenLike.ts | 2 +- packages/language-service/src/utils/SchemaGen.ts | 12 ++++++------ packages/language-service/test/piping-flows.test.ts | 12 ++++++------ 7 files changed, 30 insertions(+), 20 deletions(-) diff --git a/packages/language-service/src/cli/layerinfo.ts b/packages/language-service/src/cli/layerinfo.ts index 26a52263..2be7381e 100644 --- a/packages/language-service/src/cli/layerinfo.ts +++ b/packages/language-service/src/cli/layerinfo.ts @@ -319,7 +319,8 @@ function getExpressionName(tsApi: TypeScriptApi.TypeScriptApi, expr: ts.Expressi return getExpressionName(tsApi, expr.expression) } // Fallback: truncate the text representation - const text = expr.getSourceFile().text.substring(expr.getStart(), expr.end).replace(/\s+/g, " ") + const sourceFile = expr.getSourceFile() + const text = sourceFile.text.substring(tsApi.getTokenPosOfNode(expr, sourceFile), expr.end).replace(/\s+/g, " ") return text.length > 30 ? text.slice(0, 27) + "..." : text } diff --git a/packages/language-service/src/cli/utils/ExportedSymbols.ts b/packages/language-service/src/cli/utils/ExportedSymbols.ts index 525539b6..78a1debd 100644 --- a/packages/language-service/src/cli/utils/ExportedSymbols.ts +++ b/packages/language-service/src/cli/utils/ExportedSymbols.ts @@ -29,7 +29,10 @@ const getLocationFromDeclaration = ( ): SymbolLocation | undefined => { const sourceFile = declaration.getSourceFile() if (!sourceFile) return undefined - const { character, line } = tsInstance.getLineAndCharacterOfPosition(sourceFile, declaration.getStart()) + const { character, line } = tsInstance.getLineAndCharacterOfPosition( + sourceFile, + tsInstance.getTokenPosOfNode(declaration, sourceFile) + ) return { filePath: sourceFile.fileName, line: line + 1, diff --git a/packages/language-service/src/diagnostics/serviceNotAsClass.ts b/packages/language-service/src/diagnostics/serviceNotAsClass.ts index 05e30a80..413e45b2 100644 --- a/packages/language-service/src/diagnostics/serviceNotAsClass.ts +++ b/packages/language-service/src/diagnostics/serviceNotAsClass.ts @@ -47,15 +47,15 @@ export const serviceNotAsClass = LSP.createDiagnostic({ const variableName = ts.isIdentifier(node.name) ? ts.idText(node.name) - : sourceFile.text.substring(node.name.getStart(sourceFile), node.name.end) + : sourceFile.text.substring(ts.getTokenPosOfNode(node.name, sourceFile), node.name.end) const variableStatement = declList.parent const argsText = callExpr.arguments.length > 0 - ? callExpr.arguments.map((a) => sourceFile.text.substring(a.getStart(sourceFile), a.end)).join(", ") + ? callExpr.arguments.map((a) => sourceFile.text.substring(ts.getTokenPosOfNode(a, sourceFile), a.end)).join(", ") : "" const shapeText = typeArgs.length > 0 - ? typeArgs.map((t) => sourceFile.text.substring(t.getStart(sourceFile), t.end)).join(", ") + ? typeArgs.map((t) => sourceFile.text.substring(ts.getTokenPosOfNode(t, sourceFile), t.end)).join(", ") : "Shape" report({ diff --git a/packages/language-service/src/goto/effectRpcDefinition.ts b/packages/language-service/src/goto/effectRpcDefinition.ts index b393aedd..2886fb3a 100644 --- a/packages/language-service/src/goto/effectRpcDefinition.ts +++ b/packages/language-service/src/goto/effectRpcDefinition.ts @@ -140,7 +140,10 @@ export function effectRpcDefinition( // create the result entry for the definitions const effectRpcResult = result.map(([node]) => ({ fileName: node.getSourceFile().fileName, - textSpan: ts.createTextSpan(node.getStart(), node.end - node.getStart()), + textSpan: ts.createTextSpan( + ts.getTokenPosOfNode(node, node.getSourceFile()), + node.end - ts.getTokenPosOfNode(node, node.getSourceFile()) + ), kind: ts.ScriptElementKind.constElement, name: rpcName, containerKind: ts.ScriptElementKind.constElement, @@ -155,7 +158,10 @@ export function effectRpcDefinition( } return ({ - textSpan: ts.createTextSpan(callNode.getStart(), callNode.end - callNode.getStart()), + textSpan: ts.createTextSpan( + ts.getTokenPosOfNode(callNode, callNode.getSourceFile()), + callNode.end - ts.getTokenPosOfNode(callNode, callNode.getSourceFile()) + ), definitions: effectRpcResult }) }) diff --git a/packages/language-service/src/inlays/middlewareGenLike.ts b/packages/language-service/src/inlays/middlewareGenLike.ts index 6e68b7e4..662c7fe7 100644 --- a/packages/language-service/src/inlays/middlewareGenLike.ts +++ b/packages/language-service/src/inlays/middlewareGenLike.ts @@ -42,7 +42,7 @@ export const middlewareGenLike = Nano.fn("middlewareGenLike")(function*( const argsCloseParen = ts.findChildOfKind(_.generatorFunction, ts.SyntaxKind.CloseParenToken, sourceFile) if ( argsCloseParen && _.body && inlayHint.position >= argsCloseParen.end && - inlayHint.position <= _.body.getStart(sourceFile) + inlayHint.position <= ts.getTokenPosOfNode(_.body, sourceFile) ) { shouldOmit = true } diff --git a/packages/language-service/src/utils/SchemaGen.ts b/packages/language-service/src/utils/SchemaGen.ts index 7b426d0c..f49ce480 100644 --- a/packages/language-service/src/utils/SchemaGen.ts +++ b/packages/language-service/src/utils/SchemaGen.ts @@ -31,7 +31,7 @@ export class OnlyLiteralPropertiesSupportedError { toString() { const sourceFile = this.node.getSourceFile() return `Could not process ${ - sourceFile.text.substring(this.node.getStart(sourceFile), this.node.end) + sourceFile.text.substring(ts.getTokenPosOfNode(this.node, sourceFile), this.node.end) } as only literal properties are supported.` } } @@ -45,7 +45,7 @@ export class RequiredExplicitTypesError { toString() { const sourceFile = this.node.getSourceFile() return `Could not process ${ - sourceFile.text.substring(this.node.getStart(sourceFile), this.node.end) + sourceFile.text.substring(ts.getTokenPosOfNode(this.node, sourceFile), this.node.end) } as only explicit types are supported.` } } @@ -59,7 +59,7 @@ export class IndexSignatureWithMoreThanOneParameterError { toString() { const sourceFile = this.node.getSourceFile() return `Could not process ${ - sourceFile.text.substring(this.node.getStart(sourceFile), this.node.end) + sourceFile.text.substring(ts.getTokenPosOfNode(this.node, sourceFile), this.node.end) } as only index signatures with one parameter are supported.` } } @@ -195,7 +195,7 @@ const createUnsupportedNodeComment = ( ts.addSyntheticTrailingComment( ts.factory.createIdentifier(""), ts.SyntaxKind.MultiLineCommentTrivia, - " Not supported conversion: " + sourceFile.text.substring(node.getStart(sourceFile), node.end) + " " + " Not supported conversion: " + sourceFile.text.substring(ts.getTokenPosOfNode(node, sourceFile), node.end) + " " ) export const processNode: ( @@ -312,10 +312,10 @@ export const processNode: ( ts.isTypeQueryNode(node.objectType.type) && ts.isTypeOperatorNode(node.indexType) && node.indexType.operator === ts.SyntaxKind.KeyOfKeyword && ts.isTypeQueryNode(node.indexType.type) && sourceFile.text.substring( - node.indexType.type.exprName.getStart(sourceFile), + ts.getTokenPosOfNode(node.indexType.type.exprName, sourceFile), node.indexType.type.exprName.end ).trim() === sourceFile.text.substring( - node.objectType.type.exprName.getStart(sourceFile), + ts.getTokenPosOfNode(node.objectType.type.exprName, sourceFile), node.objectType.type.exprName.end ).trim() ) { diff --git a/packages/language-service/test/piping-flows.test.ts b/packages/language-service/test/piping-flows.test.ts index 16134d17..dd45063e 100644 --- a/packages/language-service/test/piping-flows.test.ts +++ b/packages/language-service/test/piping-flows.test.ts @@ -31,7 +31,7 @@ function formatPipingFlow( const lines: Array = [] // Get position info for the outer node - const startPos = flow.node.getStart() + const startPos = ts.getTokenPosOfNode(flow.node, sourceFile) const endPos = flow.node.getEnd() const start = ts.getLineAndCharacterOfPosition(sourceFile, startPos) const end = ts.getLineAndCharacterOfPosition(sourceFile, endPos) @@ -39,12 +39,12 @@ function formatPipingFlow( lines.push(`=== Piping Flow ===`) lines.push(`Location: ${start.line + 1}:${start.character + 1} - ${end.line + 1}:${end.character + 1}`) lines.push(`Node: ${ - sourceFile.text.substring(flow.node.getStart(sourceFile), flow.node.end).replace(/\n/g, "\\n") + sourceFile.text.substring(ts.getTokenPosOfNode(flow.node, sourceFile), flow.node.end).replace(/\n/g, "\\n") }`) lines.push(`Node Kind: ${ts.SyntaxKind[flow.node.kind]}`) lines.push(``) lines.push(`Subject: ${ - sourceFile.text.substring(flow.subject.node.getStart(sourceFile), flow.subject.node.end).replace(/\n/g, "\\n") + sourceFile.text.substring(ts.getTokenPosOfNode(flow.subject.node, sourceFile), flow.subject.node.end).replace(/\n/g, "\\n") }`) lines.push(`Subject Type: ${flow.subject.outType ? typeChecker.typeToString(flow.subject.outType) : "unknown"}`) lines.push(``) @@ -52,9 +52,9 @@ function formatPipingFlow( for (let i = 0; i < flow.transformations.length; i++) { const t = flow.transformations[i] - const calleeText = sourceFile.text.substring(t.callee.getStart(sourceFile), t.callee.end) + const calleeText = sourceFile.text.substring(ts.getTokenPosOfNode(t.callee, sourceFile), t.callee.end) const argsText = t.args - ? t.args.map((a) => sourceFile.text.substring(a.getStart(sourceFile), a.end).replace(/\n/g, "\\n")).join(", ") + ? t.args.map((a) => sourceFile.text.substring(ts.getTokenPosOfNode(a, sourceFile), a.end).replace(/\n/g, "\\n")).join(", ") : undefined const typeText = t.outType ? typeChecker.typeToString(t.outType) : "unknown" @@ -120,7 +120,7 @@ function testPipingFlowsOnExample( ), Nano.map(({ flows, reconstructPipingFlow }) => { // Sort flows by position - flows.sort((a, b) => a.node.getStart() - b.node.getStart()) + flows.sort((a, b) => ts.getTokenPosOfNode(a.node, sourceFile) - ts.getTokenPosOfNode(b.node, sourceFile)) // Format flows return flows.length === 0 ? "// no piping flows found" From 52f20d75ec4d3f26d30399f3ad5587896e2945d1 Mon Sep 17 00:00:00 2001 From: Mattia Manzati Date: Fri, 6 Mar 2026 20:23:18 +0100 Subject: [PATCH 5/7] Deprecate direct node text helpers --- .../src/core/TypeScriptApi.ts | 7 +++++ .../src/diagnostics/serviceNotAsClass.ts | 3 +- .../language-service/src/utils/SchemaGen.ts | 13 ++++---- .../test/piping-flows.test.ts | 31 ++++++++++++++----- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/packages/language-service/src/core/TypeScriptApi.ts b/packages/language-service/src/core/TypeScriptApi.ts index b9ca1e41..6282850b 100644 --- a/packages/language-service/src/core/TypeScriptApi.ts +++ b/packages/language-service/src/core/TypeScriptApi.ts @@ -183,6 +183,13 @@ declare module "typescript" { /** @deprecated Use typeChecker.getSignaturesOfType(type, ts.SignatureKind.Construct) instead */ getConstructSignatures(): ReadonlyArray } + + export interface Node { + /** @deprecated Use ts.getTokenPosOfNode(node, sourceFile) instead */ + getStart(sourceFile?: ts.SourceFile, includeJsDocComment?: boolean): number + /** @deprecated Use sourceFile.text.substring(node.pos, node.end) instead */ + getText(sourceFile?: ts.SourceFile): string + } } type _TypeScriptApi = typeof ts diff --git a/packages/language-service/src/diagnostics/serviceNotAsClass.ts b/packages/language-service/src/diagnostics/serviceNotAsClass.ts index 413e45b2..b32f2a70 100644 --- a/packages/language-service/src/diagnostics/serviceNotAsClass.ts +++ b/packages/language-service/src/diagnostics/serviceNotAsClass.ts @@ -51,7 +51,8 @@ export const serviceNotAsClass = LSP.createDiagnostic({ const variableStatement = declList.parent const argsText = callExpr.arguments.length > 0 - ? callExpr.arguments.map((a) => sourceFile.text.substring(ts.getTokenPosOfNode(a, sourceFile), a.end)).join(", ") + ? callExpr.arguments.map((a) => sourceFile.text.substring(ts.getTokenPosOfNode(a, sourceFile), a.end)) + .join(", ") : "" const shapeText = typeArgs.length > 0 diff --git a/packages/language-service/src/utils/SchemaGen.ts b/packages/language-service/src/utils/SchemaGen.ts index f49ce480..a24a9f57 100644 --- a/packages/language-service/src/utils/SchemaGen.ts +++ b/packages/language-service/src/utils/SchemaGen.ts @@ -312,12 +312,13 @@ export const processNode: ( ts.isTypeQueryNode(node.objectType.type) && ts.isTypeOperatorNode(node.indexType) && node.indexType.operator === ts.SyntaxKind.KeyOfKeyword && ts.isTypeQueryNode(node.indexType.type) && sourceFile.text.substring( - ts.getTokenPosOfNode(node.indexType.type.exprName, sourceFile), - node.indexType.type.exprName.end - ).trim() === sourceFile.text.substring( - ts.getTokenPosOfNode(node.objectType.type.exprName, sourceFile), - node.objectType.type.exprName.end - ).trim() + ts.getTokenPosOfNode(node.indexType.type.exprName, sourceFile), + node.indexType.type.exprName.end + ).trim() === + sourceFile.text.substring( + ts.getTokenPosOfNode(node.objectType.type.exprName, sourceFile), + node.objectType.type.exprName.end + ).trim() ) { const typeChecker = yield* Nano.service(TypeCheckerApi.TypeCheckerApi) const typeCheckerUtils = yield* Nano.service(TypeCheckerUtils.TypeCheckerUtils) diff --git a/packages/language-service/test/piping-flows.test.ts b/packages/language-service/test/piping-flows.test.ts index dd45063e..d3e7c766 100644 --- a/packages/language-service/test/piping-flows.test.ts +++ b/packages/language-service/test/piping-flows.test.ts @@ -38,14 +38,24 @@ function formatPipingFlow( lines.push(`=== Piping Flow ===`) lines.push(`Location: ${start.line + 1}:${start.character + 1} - ${end.line + 1}:${end.character + 1}`) - lines.push(`Node: ${ - sourceFile.text.substring(ts.getTokenPosOfNode(flow.node, sourceFile), flow.node.end).replace(/\n/g, "\\n") - }`) + lines.push( + `Node: ${ + sourceFile.text.substring(ts.getTokenPosOfNode(flow.node, sourceFile), flow.node.end).replace( + /\n/g, + "\\n" + ) + }` + ) lines.push(`Node Kind: ${ts.SyntaxKind[flow.node.kind]}`) lines.push(``) - lines.push(`Subject: ${ - sourceFile.text.substring(ts.getTokenPosOfNode(flow.subject.node, sourceFile), flow.subject.node.end).replace(/\n/g, "\\n") - }`) + lines.push( + `Subject: ${ + sourceFile.text.substring(ts.getTokenPosOfNode(flow.subject.node, sourceFile), flow.subject.node.end).replace( + /\n/g, + "\\n" + ) + }` + ) lines.push(`Subject Type: ${flow.subject.outType ? typeChecker.typeToString(flow.subject.outType) : "unknown"}`) lines.push(``) lines.push(`Transformations (${flow.transformations.length}):`) @@ -54,7 +64,14 @@ function formatPipingFlow( const t = flow.transformations[i] const calleeText = sourceFile.text.substring(ts.getTokenPosOfNode(t.callee, sourceFile), t.callee.end) const argsText = t.args - ? t.args.map((a) => sourceFile.text.substring(ts.getTokenPosOfNode(a, sourceFile), a.end).replace(/\n/g, "\\n")).join(", ") + ? t.args + .map((a) => + sourceFile.text.substring(ts.getTokenPosOfNode(a, sourceFile), a.end).replace( + /\n/g, + "\\n" + ) + ) + .join(", ") : undefined const typeText = t.outType ? typeChecker.typeToString(t.outType) : "unknown" From f3dce7f66b14021cf033c7cd6ea2006030eae21a Mon Sep 17 00:00:00 2001 From: Mattia Manzati Date: Fri, 6 Mar 2026 20:28:11 +0100 Subject: [PATCH 6/7] Adjust SchemaGen formatting --- packages/language-service/src/utils/SchemaGen.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/language-service/src/utils/SchemaGen.ts b/packages/language-service/src/utils/SchemaGen.ts index a24a9f57..995a0d67 100644 --- a/packages/language-service/src/utils/SchemaGen.ts +++ b/packages/language-service/src/utils/SchemaGen.ts @@ -31,7 +31,7 @@ export class OnlyLiteralPropertiesSupportedError { toString() { const sourceFile = this.node.getSourceFile() return `Could not process ${ - sourceFile.text.substring(ts.getTokenPosOfNode(this.node, sourceFile), this.node.end) + sourceFile.text.substring(this.node.pos, this.node.end) } as only literal properties are supported.` } } @@ -45,7 +45,7 @@ export class RequiredExplicitTypesError { toString() { const sourceFile = this.node.getSourceFile() return `Could not process ${ - sourceFile.text.substring(ts.getTokenPosOfNode(this.node, sourceFile), this.node.end) + sourceFile.text.substring(this.node.pos, this.node.end) } as only explicit types are supported.` } } @@ -59,7 +59,7 @@ export class IndexSignatureWithMoreThanOneParameterError { toString() { const sourceFile = this.node.getSourceFile() return `Could not process ${ - sourceFile.text.substring(ts.getTokenPosOfNode(this.node, sourceFile), this.node.end) + sourceFile.text.substring(this.node.pos, this.node.end) } as only index signatures with one parameter are supported.` } } From 46307b259a4252be03216652a9db0db126bfd683 Mon Sep 17 00:00:00 2001 From: Mattia Manzati Date: Fri, 6 Mar 2026 20:56:32 +0100 Subject: [PATCH 7/7] Update completions snapshot --- .../harness-effect-v3/__snapshots__/completions.test.ts.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/harness-effect-v3/__snapshots__/completions.test.ts.snap b/packages/harness-effect-v3/__snapshots__/completions.test.ts.snap index b2147575..f8da2e52 100644 --- a/packages/harness-effect-v3/__snapshots__/completions.test.ts.snap +++ b/packages/harness-effect-v3/__snapshots__/completions.test.ts.snap @@ -248,7 +248,7 @@ exports[`Completion effectDataClasses > effectDataClasses_directImportTaggedErro exports[`Completion effectDiagnosticsComment > effectDiagnosticsComment.ts at 2:5 1`] = ` [ { - "insertText": "@effect-diagnostics \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", + "insertText": "@effect-diagnostics \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", "isSnippet": true, "kind": "string", "name": "@effect-diagnostics", @@ -259,7 +259,7 @@ exports[`Completion effectDiagnosticsComment > effectDiagnosticsComment.ts at 2: "sortText": "11", }, { - "insertText": "@effect-diagnostics-next-line \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", + "insertText": "@effect-diagnostics-next-line \${1|anyUnknownInErrorContext,catchAllToMapError,catchUnfailableEffect,classSelfMismatch,deterministicKeys,duplicatePackage,effectFnIife,effectFnOpportunity,effectGenUsesAdapter,effectInFailure,effectInVoidSuccess,effectMapVoid,effectSucceedWithVoid,extendsNativeError,floatingEffect,genericEffectServices,globalErrorInEffectCatch,globalErrorInEffectFailure,importFromBarrel,instanceOfSchema,layerMergeAllWithDependencies,leakingRequirements,missedPipeableOpportunity,missingEffectContext,missingEffectError,missingEffectServiceDependency,missingLayerContext,missingReturnYieldStar,missingStarInYieldEffectGen,multipleEffectProvide,nonObjectEffectServiceType,outdatedApi,outdatedEffectCodegen,overriddenSchemaConstructor,preferSchemaOverJson,redundantSchemaTagIdentifier,returnEffectInGen,runEffectInsideEffect,schemaStructWithTag,schemaSyncInEffect,schemaUnionOfLiterals,scopeInLayerEffect,serviceNotAsClass,strictBooleanExpressions,strictEffectProvide,tryCatchInEffectGen,unknownInEffectCatch,unnecessaryEffectGen,unnecessaryFailYieldableError,unnecessaryPipe,unnecessaryPipeChain,unsupportedServiceAccessors|}:\${2|off,warning,error,message,suggestion|}$0", "isSnippet": true, "kind": "string", "name": "@effect-diagnostics-next-line",