Skip to content

Commit 8580bed

Browse files
Fix false positive regarding Schema.Class (#385)
1 parent 62b9829 commit 8580bed

File tree

7 files changed

+112
-10
lines changed

7 files changed

+112
-10
lines changed

.changeset/salty-turkeys-vanish.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@effect/language-service": patch
3+
---
4+
5+
Fix false positive regarding Schema.Class
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import * as Persistable from "./utils"
2+
3+
export class TTLRequest extends Persistable.Class<{
4+
payload: { id: number }
5+
}>()("TTLRequest", {
6+
primaryKey: (req) => `TTLRequest:${req.id}`
7+
}) {}

examples/diagnostics/utils.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type * as Types from "effect/Types"
2+
3+
export const Class = <
4+
Config extends {
5+
payload: Record<string, unknown>
6+
requires?: any
7+
requestError?: any
8+
} = { payload: {} }
9+
>() =>
10+
<const Tag extends string>(tag: Tag, _options: {
11+
readonly primaryKey: (payload: Config["payload"]) => string
12+
}): new(
13+
args: Types.EqualsWith<
14+
Config["payload"],
15+
{},
16+
void,
17+
{
18+
readonly [
19+
P in keyof Config["payload"] as P extends "_tag" ? never : P
20+
]: Config["payload"][P]
21+
}
22+
>
23+
) =>
24+
& { readonly _tag: Tag }
25+
& { readonly [K in keyof Config["payload"]]: Config["payload"][K] } =>
26+
{
27+
function Persistable(this: any, props: any) {
28+
this._tag = tag
29+
if (props) {
30+
Object.assign(this, props)
31+
}
32+
}
33+
return Persistable as any
34+
}

src/core/TypeParser.ts

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -484,15 +484,30 @@ export function make(
484484
Nano.fn("TypeParser.importedSchemaModule")(function*(
485485
node: ts.Node
486486
) {
487+
// should be an expression
488+
if (!ts.isIdentifier(node)) {
489+
return yield* typeParserIssue("Node is not an expression", undefined, node)
490+
}
487491
const type = typeChecker.getTypeAtLocation(node)
488492
// if the type has a property "Class" that is a function
489493
const propertySymbol = typeChecker.getPropertyOfType(type, "Class")
490494
if (!propertySymbol) {
491495
return yield* typeParserIssue("Type has no 'Class' property", type, node)
492496
}
493-
// should be an expression
494-
if (!ts.isExpression(node)) {
495-
return yield* typeParserIssue("Node is not an expression", type, node)
497+
const sourceFile = tsUtils.getSourceFileOfNode(node)
498+
if (!sourceFile) {
499+
return yield* typeParserIssue("Node is not in a source file", undefined, node)
500+
}
501+
const schemaIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
502+
sourceFile,
503+
"effect",
504+
"Schema"
505+
)
506+
if (!schemaIdentifier) {
507+
return yield* typeParserIssue("Schema module not found", undefined, node)
508+
}
509+
if (ts.idText(node) !== schemaIdentifier) {
510+
return yield* typeParserIssue("Node is not a schema module reference", undefined, node)
496511
}
497512
// return the node itself
498513
return node
@@ -512,8 +527,23 @@ export function make(
512527
return yield* typeParserIssue("Type has no 'Tag' property", type, node)
513528
}
514529
// should be an expression
515-
if (!ts.isExpression(node)) {
516-
return yield* typeParserIssue("Node is not an expression", type, node)
530+
if (!ts.isIdentifier(node)) {
531+
return yield* typeParserIssue("Node is not an identifier", type, node)
532+
}
533+
const sourceFile = tsUtils.getSourceFileOfNode(node)
534+
if (!sourceFile) {
535+
return yield* typeParserIssue("Node is not in a source file", undefined, node)
536+
}
537+
const contextIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
538+
sourceFile,
539+
"effect",
540+
"Context"
541+
)
542+
if (!contextIdentifier) {
543+
return yield* typeParserIssue("Context module not found", undefined, node)
544+
}
545+
if (ts.idText(node) !== contextIdentifier) {
546+
return yield* typeParserIssue("Node is not a context module reference", undefined, node)
517547
}
518548
// return the node itself
519549
return node
@@ -557,9 +587,24 @@ export function make(
557587
return yield* typeParserIssue("Type has no 'TaggedError' property", type, node)
558588
}
559589
// should be an expression
560-
if (!ts.isExpression(node)) {
590+
if (!ts.isIdentifier(node)) {
561591
return yield* typeParserIssue("Node is not an expression", type, node)
562592
}
593+
const sourceFile = tsUtils.getSourceFileOfNode(node)
594+
if (!sourceFile) {
595+
return yield* typeParserIssue("Node is not in a source file", undefined, node)
596+
}
597+
const dataIdentifier = tsUtils.findImportedModuleIdentifierByPackageAndNameOrBarrel(
598+
sourceFile,
599+
"effect",
600+
"Data"
601+
)
602+
if (!dataIdentifier) {
603+
return yield* typeParserIssue("Data module not found", undefined, node)
604+
}
605+
if (ts.idText(node) !== dataIdentifier) {
606+
return yield* typeParserIssue("Node is not a data module reference", undefined, node)
607+
}
563608
// return the node itself
564609
return node
565610
}),
@@ -1029,8 +1074,10 @@ export function make(
10291074
ts.isPropertyAccessExpression(schemaIdentifier) && ts.isIdentifier(schemaIdentifier.name) &&
10301075
ts.idText(schemaIdentifier.name) === "Class"
10311076
) {
1077+
const expressionType = typeChecker.getTypeAtLocation(expression)
10321078
const parsedSchemaModule = yield* pipe(
1033-
importedSchemaModule(schemaIdentifier.expression),
1079+
effectSchemaType(expressionType, expression),
1080+
Nano.flatMap(() => importedSchemaModule(schemaIdentifier.expression)),
10341081
Nano.option
10351082
)
10361083
if (Option.isSome(parsedSchemaModule)) {
@@ -1081,8 +1128,10 @@ export function make(
10811128
ts.isPropertyAccessExpression(schemaIdentifier) && ts.isIdentifier(schemaIdentifier.name) &&
10821129
ts.idText(schemaIdentifier.name) === "TaggedClass"
10831130
) {
1131+
const expressionType = typeChecker.getTypeAtLocation(expression)
10841132
const parsedSchemaModule = yield* pipe(
1085-
importedSchemaModule(schemaIdentifier.expression),
1133+
effectSchemaType(expressionType, expression),
1134+
Nano.flatMap(() => importedSchemaModule(schemaIdentifier.expression)),
10861135
Nano.option
10871136
)
10881137
if (Option.isSome(parsedSchemaModule)) {
@@ -1141,8 +1190,10 @@ export function make(
11411190
ts.isPropertyAccessExpression(schemaIdentifier) && ts.isIdentifier(schemaIdentifier.name) &&
11421191
ts.idText(schemaIdentifier.name) === "TaggedError"
11431192
) {
1193+
const expressionType = typeChecker.getTypeAtLocation(expression)
11441194
const parsedSchemaModule = yield* pipe(
1145-
importedSchemaModule(schemaIdentifier.expression),
1195+
effectSchemaType(expressionType, expression),
1196+
Nano.flatMap(() => importedSchemaModule(schemaIdentifier.expression)),
11461197
Nano.option
11471198
)
11481199
if (Option.isSome(parsedSchemaModule)) {
@@ -1306,8 +1357,10 @@ export function make(
13061357
ts.isPropertyAccessExpression(schemaIdentifier) && ts.isIdentifier(schemaIdentifier.name) &&
13071358
ts.idText(schemaIdentifier.name) === "TaggedRequest"
13081359
) {
1360+
const expressionType = typeChecker.getTypeAtLocation(expression)
13091361
const parsedSchemaModule = yield* pipe(
1310-
importedSchemaModule(schemaIdentifier.expression),
1362+
effectSchemaType(expressionType, expression),
1363+
Nano.flatMap(() => importedSchemaModule(schemaIdentifier.expression)),
13111364
Nano.option
13121365
)
13131366
if (Option.isSome(parsedSchemaModule)) {

src/diagnostics/classSelfMismatch.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export const classSelfMismatch = LSP.createDiagnostic({
2929
const result = yield* pipe(
3030
typeParser.extendsEffectService(node),
3131
Nano.orElse(() => typeParser.extendsContextTag(node)),
32+
Nano.orElse(() => typeParser.extendsEffectTag(node)),
3233
Nano.orElse(() => typeParser.extendsSchemaClass(node)),
3334
Nano.orElse(() => typeParser.extendsSchemaTaggedClass(node)),
3435
Nano.orElse(() => typeParser.extendsSchemaTaggedError(node)),
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
no codefixes
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// no diagnostics

0 commit comments

Comments
 (0)