@effect/language-service 0.80.0 → 0.82.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 +71 -56
- package/cli.js +1974 -270
- package/cli.js.map +1 -1
- package/effect-lsp-patch-utils.js +217 -6
- package/effect-lsp-patch-utils.js.map +1 -1
- package/index.js +217 -6
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/schema.json +2828 -0
- package/transform.js +217 -6
- package/transform.js.map +1 -1
package/cli.js
CHANGED
|
@@ -25719,7 +25719,7 @@ var runWith2 = (command, config) => {
|
|
|
25719
25719
|
// package.json
|
|
25720
25720
|
var package_default = {
|
|
25721
25721
|
name: "@effect/language-service",
|
|
25722
|
-
version: "0.
|
|
25722
|
+
version: "0.82.0",
|
|
25723
25723
|
publishConfig: {
|
|
25724
25724
|
access: "public",
|
|
25725
25725
|
directory: "dist"
|
|
@@ -31800,6 +31800,7 @@ var anyUnknownInErrorContext = createDiagnostic({
|
|
|
31800
31800
|
name: "anyUnknownInErrorContext",
|
|
31801
31801
|
code: 28,
|
|
31802
31802
|
description: "Detects 'any' or 'unknown' types in Effect error or requirements channels",
|
|
31803
|
+
group: "correctness",
|
|
31803
31804
|
severity: "off",
|
|
31804
31805
|
fixable: false,
|
|
31805
31806
|
supportedEffect: ["v3", "v4"],
|
|
@@ -31905,6 +31906,7 @@ var catchAllToMapError = createDiagnostic({
|
|
|
31905
31906
|
name: "catchAllToMapError",
|
|
31906
31907
|
code: 39,
|
|
31907
31908
|
description: "Suggests using Effect.mapError instead of Effect.catchAll when the callback only wraps the error with Effect.fail",
|
|
31909
|
+
group: "style",
|
|
31908
31910
|
severity: "suggestion",
|
|
31909
31911
|
fixable: true,
|
|
31910
31912
|
supportedEffect: ["v3", "v4"],
|
|
@@ -32004,6 +32006,7 @@ var catchUnfailableEffect = createDiagnostic({
|
|
|
32004
32006
|
name: "catchUnfailableEffect",
|
|
32005
32007
|
code: 2,
|
|
32006
32008
|
description: "Warns when using error handling on Effects that never fail (error type is 'never')",
|
|
32009
|
+
group: "antipattern",
|
|
32007
32010
|
severity: "suggestion",
|
|
32008
32011
|
fixable: false,
|
|
32009
32012
|
supportedEffect: ["v3", "v4"],
|
|
@@ -32055,6 +32058,7 @@ var classSelfMismatch = createDiagnostic({
|
|
|
32055
32058
|
name: "classSelfMismatch",
|
|
32056
32059
|
code: 20,
|
|
32057
32060
|
description: "Ensures Self type parameter matches the class name in Service/Tag/Schema classes",
|
|
32061
|
+
group: "correctness",
|
|
32058
32062
|
severity: "error",
|
|
32059
32063
|
fixable: true,
|
|
32060
32064
|
supportedEffect: ["v3", "v4"],
|
|
@@ -32072,6 +32076,7 @@ var classSelfMismatch = createDiagnostic({
|
|
|
32072
32076
|
if (ts.isClassDeclaration(node) && node.name && node.heritageClauses) {
|
|
32073
32077
|
const result3 = yield* pipe(
|
|
32074
32078
|
typeParser.extendsEffectService(node),
|
|
32079
|
+
orElse5(() => typeParser.extendsServiceMapService(node)),
|
|
32075
32080
|
orElse5(() => typeParser.extendsContextTag(node)),
|
|
32076
32081
|
orElse5(() => typeParser.extendsEffectTag(node)),
|
|
32077
32082
|
orElse5(() => typeParser.extendsSchemaClass(node)),
|
|
@@ -32195,6 +32200,7 @@ var deterministicKeys = createDiagnostic({
|
|
|
32195
32200
|
name: "deterministicKeys",
|
|
32196
32201
|
code: 25,
|
|
32197
32202
|
description: "Enforces deterministic naming for service/tag/error identifiers based on class names",
|
|
32203
|
+
group: "style",
|
|
32198
32204
|
severity: "off",
|
|
32199
32205
|
fixable: true,
|
|
32200
32206
|
supportedEffect: ["v3", "v4"],
|
|
@@ -32314,6 +32320,7 @@ var duplicatePackage = createDiagnostic({
|
|
|
32314
32320
|
name: "duplicatePackage",
|
|
32315
32321
|
code: 6,
|
|
32316
32322
|
description: "Detects when multiple versions of the same Effect package are loaded",
|
|
32323
|
+
group: "correctness",
|
|
32317
32324
|
severity: "warning",
|
|
32318
32325
|
fixable: false,
|
|
32319
32326
|
supportedEffect: ["v3", "v4"],
|
|
@@ -32345,6 +32352,7 @@ var effectFnIife = createDiagnostic({
|
|
|
32345
32352
|
name: "effectFnIife",
|
|
32346
32353
|
code: 46,
|
|
32347
32354
|
description: "Effect.fn or Effect.fnUntraced is called as an IIFE (Immediately Invoked Function Expression). Use Effect.gen instead.",
|
|
32355
|
+
group: "antipattern",
|
|
32348
32356
|
severity: "warning",
|
|
32349
32357
|
fixable: true,
|
|
32350
32358
|
supportedEffect: ["v3", "v4"],
|
|
@@ -32444,11 +32452,95 @@ var effectFnIife = createDiagnostic({
|
|
|
32444
32452
|
})
|
|
32445
32453
|
});
|
|
32446
32454
|
|
|
32455
|
+
// src/diagnostics/effectFnImplicitAny.ts
|
|
32456
|
+
var getParameterName = (typescript, name) => {
|
|
32457
|
+
if (typescript.isIdentifier(name)) {
|
|
32458
|
+
return typescript.idText(name);
|
|
32459
|
+
}
|
|
32460
|
+
return "parameter";
|
|
32461
|
+
};
|
|
32462
|
+
var hasOuterContextualFunctionType = (typescript, typeChecker, node) => {
|
|
32463
|
+
const contextualType = typeChecker.getContextualType(node);
|
|
32464
|
+
if (!contextualType) {
|
|
32465
|
+
return false;
|
|
32466
|
+
}
|
|
32467
|
+
return typeChecker.getSignaturesOfType(contextualType, typescript.SignatureKind.Call).length > 0;
|
|
32468
|
+
};
|
|
32469
|
+
var effectFnImplicitAny = createDiagnostic({
|
|
32470
|
+
name: "effectFnImplicitAny",
|
|
32471
|
+
code: 54,
|
|
32472
|
+
description: "Mirrors noImplicitAny for unannotated Effect.fn and Effect.fnUntraced callback parameters when no outer contextual function type exists",
|
|
32473
|
+
group: "correctness",
|
|
32474
|
+
severity: "error",
|
|
32475
|
+
fixable: false,
|
|
32476
|
+
supportedEffect: ["v3", "v4"],
|
|
32477
|
+
apply: fn3("effectFnImplicitAny.apply")(function* (sourceFile, report) {
|
|
32478
|
+
const ts = yield* service2(TypeScriptApi);
|
|
32479
|
+
const program = yield* service2(TypeScriptProgram);
|
|
32480
|
+
const typeChecker = yield* service2(TypeCheckerApi);
|
|
32481
|
+
const typeParser = yield* service2(TypeParser);
|
|
32482
|
+
const noImplicitAny = program.getCompilerOptions().noImplicitAny ?? program.getCompilerOptions().strict ?? false;
|
|
32483
|
+
if (!noImplicitAny) {
|
|
32484
|
+
return;
|
|
32485
|
+
}
|
|
32486
|
+
const nodeToVisit = [sourceFile];
|
|
32487
|
+
const appendNodeToVisit = (node) => {
|
|
32488
|
+
nodeToVisit.push(node);
|
|
32489
|
+
return void 0;
|
|
32490
|
+
};
|
|
32491
|
+
while (nodeToVisit.length > 0) {
|
|
32492
|
+
const node = nodeToVisit.pop();
|
|
32493
|
+
ts.forEachChild(node, appendNodeToVisit);
|
|
32494
|
+
const parsed = yield* pipe(
|
|
32495
|
+
typeParser.effectFn(node),
|
|
32496
|
+
map12((result3) => ({
|
|
32497
|
+
call: result3.node,
|
|
32498
|
+
fn: result3.regularFunction
|
|
32499
|
+
})),
|
|
32500
|
+
orElse5(
|
|
32501
|
+
() => pipe(
|
|
32502
|
+
typeParser.effectFnGen(node),
|
|
32503
|
+
map12((result3) => ({
|
|
32504
|
+
call: result3.node,
|
|
32505
|
+
fn: result3.generatorFunction
|
|
32506
|
+
}))
|
|
32507
|
+
)
|
|
32508
|
+
),
|
|
32509
|
+
orElse5(
|
|
32510
|
+
() => pipe(
|
|
32511
|
+
typeParser.effectFnUntracedGen(node),
|
|
32512
|
+
map12((result3) => ({
|
|
32513
|
+
call: result3.node,
|
|
32514
|
+
fn: result3.generatorFunction
|
|
32515
|
+
}))
|
|
32516
|
+
)
|
|
32517
|
+
),
|
|
32518
|
+
orUndefined
|
|
32519
|
+
);
|
|
32520
|
+
if (!parsed || hasOuterContextualFunctionType(ts, typeChecker, parsed.call)) {
|
|
32521
|
+
continue;
|
|
32522
|
+
}
|
|
32523
|
+
for (const parameter of parsed.fn.parameters) {
|
|
32524
|
+
if (parameter.type || parameter.initializer) {
|
|
32525
|
+
continue;
|
|
32526
|
+
}
|
|
32527
|
+
const parameterName = getParameterName(ts, parameter.name);
|
|
32528
|
+
report({
|
|
32529
|
+
location: parameter.name,
|
|
32530
|
+
messageText: `Parameter '${parameterName}' implicitly has an 'any' type in Effect.fn/Effect.fnUntraced. Add an explicit type annotation or provide a contextual function type.`,
|
|
32531
|
+
fixes: []
|
|
32532
|
+
});
|
|
32533
|
+
}
|
|
32534
|
+
}
|
|
32535
|
+
})
|
|
32536
|
+
});
|
|
32537
|
+
|
|
32447
32538
|
// src/diagnostics/effectFnOpportunity.ts
|
|
32448
32539
|
var effectFnOpportunity = createDiagnostic({
|
|
32449
32540
|
name: "effectFnOpportunity",
|
|
32450
32541
|
code: 41,
|
|
32451
32542
|
description: "Suggests using Effect.fn for functions that returns an Effect",
|
|
32543
|
+
group: "style",
|
|
32452
32544
|
severity: "suggestion",
|
|
32453
32545
|
fixable: true,
|
|
32454
32546
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33043,6 +33135,7 @@ var effectGenUsesAdapter = createDiagnostic({
|
|
|
33043
33135
|
name: "effectGenUsesAdapter",
|
|
33044
33136
|
code: 23,
|
|
33045
33137
|
description: "Warns when using the deprecated adapter parameter in Effect.gen",
|
|
33138
|
+
group: "antipattern",
|
|
33046
33139
|
severity: "warning",
|
|
33047
33140
|
fixable: false,
|
|
33048
33141
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33083,6 +33176,7 @@ var effectInFailure = createDiagnostic({
|
|
|
33083
33176
|
name: "effectInFailure",
|
|
33084
33177
|
code: 49,
|
|
33085
33178
|
description: "Warns when an Effect is used inside an Effect failure channel",
|
|
33179
|
+
group: "antipattern",
|
|
33086
33180
|
severity: "warning",
|
|
33087
33181
|
fixable: false,
|
|
33088
33182
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33149,6 +33243,7 @@ var effectInVoidSuccess = createDiagnostic({
|
|
|
33149
33243
|
name: "effectInVoidSuccess",
|
|
33150
33244
|
code: 14,
|
|
33151
33245
|
description: "Detects nested Effects in void success channels that may cause unexecuted effects",
|
|
33246
|
+
group: "antipattern",
|
|
33152
33247
|
severity: "warning",
|
|
33153
33248
|
fixable: false,
|
|
33154
33249
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33200,6 +33295,7 @@ var effectMapVoid = createDiagnostic({
|
|
|
33200
33295
|
name: "effectMapVoid",
|
|
33201
33296
|
code: 40,
|
|
33202
33297
|
description: "Suggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {})",
|
|
33298
|
+
group: "style",
|
|
33203
33299
|
severity: "suggestion",
|
|
33204
33300
|
fixable: true,
|
|
33205
33301
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33266,6 +33362,7 @@ var effectSucceedWithVoid = createDiagnostic({
|
|
|
33266
33362
|
name: "effectSucceedWithVoid",
|
|
33267
33363
|
code: 47,
|
|
33268
33364
|
description: "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
|
|
33365
|
+
group: "style",
|
|
33269
33366
|
severity: "suggestion",
|
|
33270
33367
|
fixable: true,
|
|
33271
33368
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33319,6 +33416,7 @@ var extendsNativeError = createDiagnostic({
|
|
|
33319
33416
|
name: "extendsNativeError",
|
|
33320
33417
|
code: 50,
|
|
33321
33418
|
description: "Warns when a class directly extends the native Error class",
|
|
33419
|
+
group: "effectNative",
|
|
33322
33420
|
severity: "off",
|
|
33323
33421
|
fixable: false,
|
|
33324
33422
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33371,6 +33469,7 @@ var floatingEffect = createDiagnostic({
|
|
|
33371
33469
|
name: "floatingEffect",
|
|
33372
33470
|
code: 3,
|
|
33373
33471
|
description: "Ensures Effects are yielded or assigned to variables, not left floating",
|
|
33472
|
+
group: "correctness",
|
|
33374
33473
|
severity: "error",
|
|
33375
33474
|
fixable: false,
|
|
33376
33475
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33424,6 +33523,7 @@ var genericEffectServices = createDiagnostic({
|
|
|
33424
33523
|
name: "genericEffectServices",
|
|
33425
33524
|
code: 10,
|
|
33426
33525
|
description: "Prevents services with type parameters that cannot be discriminated at runtime",
|
|
33526
|
+
group: "correctness",
|
|
33427
33527
|
severity: "warning",
|
|
33428
33528
|
fixable: false,
|
|
33429
33529
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33473,6 +33573,7 @@ var globalErrorInEffectCatch = createDiagnostic({
|
|
|
33473
33573
|
name: "globalErrorInEffectCatch",
|
|
33474
33574
|
code: 36,
|
|
33475
33575
|
description: "Warns when catch callbacks return global Error type instead of typed errors",
|
|
33576
|
+
group: "antipattern",
|
|
33476
33577
|
severity: "warning",
|
|
33477
33578
|
fixable: false,
|
|
33478
33579
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33535,6 +33636,7 @@ var globalErrorInEffectFailure = createDiagnostic({
|
|
|
33535
33636
|
name: "globalErrorInEffectFailure",
|
|
33536
33637
|
code: 35,
|
|
33537
33638
|
description: "Warns when the global Error type is used in an Effect failure channel",
|
|
33639
|
+
group: "antipattern",
|
|
33538
33640
|
severity: "warning",
|
|
33539
33641
|
fixable: false,
|
|
33540
33642
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33585,11 +33687,54 @@ var globalErrorInEffectFailure = createDiagnostic({
|
|
|
33585
33687
|
})
|
|
33586
33688
|
});
|
|
33587
33689
|
|
|
33690
|
+
// src/diagnostics/globalFetch.ts
|
|
33691
|
+
var globalFetch = createDiagnostic({
|
|
33692
|
+
name: "globalFetch",
|
|
33693
|
+
code: 53,
|
|
33694
|
+
description: "Warns when using the global fetch function instead of the Effect HTTP client",
|
|
33695
|
+
group: "effectNative",
|
|
33696
|
+
severity: "off",
|
|
33697
|
+
fixable: false,
|
|
33698
|
+
supportedEffect: ["v3", "v4"],
|
|
33699
|
+
apply: fn3("globalFetch.apply")(function* (sourceFile, report) {
|
|
33700
|
+
const ts = yield* service2(TypeScriptApi);
|
|
33701
|
+
const typeChecker = yield* service2(TypeCheckerApi);
|
|
33702
|
+
const typeParser = yield* service2(TypeParser);
|
|
33703
|
+
const fetchSymbol = typeChecker.resolveName("fetch", void 0, ts.SymbolFlags.Value, false);
|
|
33704
|
+
if (!fetchSymbol) return;
|
|
33705
|
+
const effectVersion = typeParser.supportedEffect();
|
|
33706
|
+
const packageName = effectVersion === "v3" ? "@effect/platform" : "effect/unstable/http";
|
|
33707
|
+
const messageText = `Prefer using HttpClient from ${packageName} instead of the global 'fetch' function.`;
|
|
33708
|
+
const nodeToVisit = [];
|
|
33709
|
+
const appendNodeToVisit = (node) => {
|
|
33710
|
+
nodeToVisit.push(node);
|
|
33711
|
+
return void 0;
|
|
33712
|
+
};
|
|
33713
|
+
ts.forEachChild(sourceFile, appendNodeToVisit);
|
|
33714
|
+
while (nodeToVisit.length > 0) {
|
|
33715
|
+
const node = nodeToVisit.shift();
|
|
33716
|
+
if (ts.isCallExpression(node)) {
|
|
33717
|
+
const symbol4 = typeChecker.getSymbolAtLocation(node.expression);
|
|
33718
|
+
const resolvedSymbol = symbol4 && symbol4.flags & ts.SymbolFlags.Alias ? typeChecker.getAliasedSymbol(symbol4) : symbol4;
|
|
33719
|
+
if (resolvedSymbol === fetchSymbol) {
|
|
33720
|
+
report({
|
|
33721
|
+
location: node.expression,
|
|
33722
|
+
messageText,
|
|
33723
|
+
fixes: []
|
|
33724
|
+
});
|
|
33725
|
+
}
|
|
33726
|
+
}
|
|
33727
|
+
ts.forEachChild(node, appendNodeToVisit);
|
|
33728
|
+
}
|
|
33729
|
+
})
|
|
33730
|
+
});
|
|
33731
|
+
|
|
33588
33732
|
// src/diagnostics/importFromBarrel.ts
|
|
33589
33733
|
var importFromBarrel = createDiagnostic({
|
|
33590
33734
|
name: "importFromBarrel",
|
|
33591
33735
|
code: 12,
|
|
33592
33736
|
description: "Suggests importing from specific module paths instead of barrel exports",
|
|
33737
|
+
group: "style",
|
|
33593
33738
|
severity: "off",
|
|
33594
33739
|
fixable: true,
|
|
33595
33740
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33732,6 +33877,7 @@ var instanceOfSchema = createDiagnostic({
|
|
|
33732
33877
|
name: "instanceOfSchema",
|
|
33733
33878
|
code: 45,
|
|
33734
33879
|
description: "Suggests using Schema.is instead of instanceof for Effect Schema types",
|
|
33880
|
+
group: "effectNative",
|
|
33735
33881
|
severity: "off",
|
|
33736
33882
|
fixable: true,
|
|
33737
33883
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33797,6 +33943,7 @@ var layerMergeAllWithDependencies = createDiagnostic({
|
|
|
33797
33943
|
name: "layerMergeAllWithDependencies",
|
|
33798
33944
|
code: 37,
|
|
33799
33945
|
description: "Detects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires",
|
|
33946
|
+
group: "antipattern",
|
|
33800
33947
|
severity: "warning",
|
|
33801
33948
|
fixable: true,
|
|
33802
33949
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33912,6 +34059,7 @@ var leakingRequirements = createDiagnostic({
|
|
|
33912
34059
|
name: "leakingRequirements",
|
|
33913
34060
|
code: 8,
|
|
33914
34061
|
description: "Detects implementation services leaked in service methods",
|
|
34062
|
+
group: "antipattern",
|
|
33915
34063
|
severity: "suggestion",
|
|
33916
34064
|
fixable: false,
|
|
33917
34065
|
supportedEffect: ["v3", "v4"],
|
|
@@ -33921,6 +34069,24 @@ var leakingRequirements = createDiagnostic({
|
|
|
33921
34069
|
const typeCheckerUtils = yield* service2(TypeCheckerUtils);
|
|
33922
34070
|
const typeParser = yield* service2(TypeParser);
|
|
33923
34071
|
const tsUtils = yield* service2(TypeScriptUtils);
|
|
34072
|
+
const isExpectedLeakingServiceSuppressed = (leakedServiceName, startNode) => {
|
|
34073
|
+
const sourceFile2 = tsUtils.getSourceFileOfNode(startNode);
|
|
34074
|
+
if (!sourceFile2) return false;
|
|
34075
|
+
return !!ts.findAncestor(startNode, (current) => {
|
|
34076
|
+
const ranges = ts.getLeadingCommentRanges(sourceFile2.text, current.pos) ?? [];
|
|
34077
|
+
const isSuppressed = ranges.some((range2) => {
|
|
34078
|
+
const commentText = sourceFile2.text.slice(range2.pos, range2.end);
|
|
34079
|
+
return commentText.split("\n").filter((line) => line.includes("@effect-expect-leaking")).some((line) => {
|
|
34080
|
+
const markerIndex = line.indexOf("@effect-expect-leaking");
|
|
34081
|
+
return markerIndex !== -1 && line.slice(markerIndex + "@effect-expect-leaking".length).includes(leakedServiceName);
|
|
34082
|
+
});
|
|
34083
|
+
});
|
|
34084
|
+
if (isSuppressed) {
|
|
34085
|
+
return true;
|
|
34086
|
+
}
|
|
34087
|
+
return ts.isClassDeclaration(current) || ts.isVariableStatement(current) || ts.isExpressionStatement(current) || ts.isStatement(current) ? "quit" : false;
|
|
34088
|
+
});
|
|
34089
|
+
};
|
|
33924
34090
|
const parseLeakedRequirements = cachedBy(
|
|
33925
34091
|
fn3("leakingServices.checkServiceLeaking")(
|
|
33926
34092
|
function* (service3, atLocation) {
|
|
@@ -34002,8 +34168,12 @@ var leakingRequirements = createDiagnostic({
|
|
|
34002
34168
|
(_, service3) => service3
|
|
34003
34169
|
);
|
|
34004
34170
|
function reportLeakingRequirements(node, requirements) {
|
|
34005
|
-
|
|
34006
|
-
|
|
34171
|
+
const filteredRequirements = requirements.filter(
|
|
34172
|
+
(requirement) => !isExpectedLeakingServiceSuppressed(typeChecker.typeToString(requirement), node)
|
|
34173
|
+
);
|
|
34174
|
+
if (filteredRequirements.length === 0) return;
|
|
34175
|
+
const requirementsStr = filteredRequirements.map((_) => typeChecker.typeToString(_)).join(" | ");
|
|
34176
|
+
const firstStr = typeChecker.typeToString(filteredRequirements[0]);
|
|
34007
34177
|
report({
|
|
34008
34178
|
location: node,
|
|
34009
34179
|
messageText: `Methods of this Service require \`${requirementsStr}\` from every caller.
|
|
@@ -34012,7 +34182,7 @@ This leaks implementation details into the service's public type \u2014 callers
|
|
|
34012
34182
|
|
|
34013
34183
|
Resolve these dependencies at Layer creation and provide them to each method, so the service's type reflects its purpose, not its implementation.
|
|
34014
34184
|
|
|
34015
|
-
To suppress this diagnostic for specific dependency types that are intentionally passed through (e.g., HttpServerRequest), add \`@effect-leakable-service\` JSDoc to their interface declarations (e.g., the \`${
|
|
34185
|
+
To suppress this diagnostic for specific dependency types that are intentionally passed through (e.g., HttpServerRequest), add \`@effect-leakable-service\` JSDoc to their interface declarations (e.g., the \`${firstStr}\` interface), or to this service by adding a \`@effect-expect-leaking ${firstStr}\` JSDoc.
|
|
34016
34186
|
|
|
34017
34187
|
More info and examples at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage`,
|
|
34018
34188
|
fixes: []
|
|
@@ -34068,6 +34238,7 @@ var missedPipeableOpportunity = createDiagnostic({
|
|
|
34068
34238
|
name: "missedPipeableOpportunity",
|
|
34069
34239
|
code: 26,
|
|
34070
34240
|
description: "Enforces the use of pipeable style for nested function calls",
|
|
34241
|
+
group: "style",
|
|
34071
34242
|
severity: "off",
|
|
34072
34243
|
fixable: true,
|
|
34073
34244
|
supportedEffect: ["v3", "v4"],
|
|
@@ -34250,6 +34421,7 @@ var missingEffectContext = createDiagnostic({
|
|
|
34250
34421
|
name: "missingEffectContext",
|
|
34251
34422
|
code: 1,
|
|
34252
34423
|
description: "Reports missing service requirements in Effect context channel",
|
|
34424
|
+
group: "correctness",
|
|
34253
34425
|
severity: "error",
|
|
34254
34426
|
fixable: false,
|
|
34255
34427
|
supportedEffect: ["v3", "v4"],
|
|
@@ -34301,6 +34473,7 @@ var missingEffectError = createDiagnostic({
|
|
|
34301
34473
|
name: "missingEffectError",
|
|
34302
34474
|
code: 1,
|
|
34303
34475
|
description: "Reports missing error types in Effect error channel",
|
|
34476
|
+
group: "correctness",
|
|
34304
34477
|
severity: "error",
|
|
34305
34478
|
fixable: true,
|
|
34306
34479
|
supportedEffect: ["v3", "v4"],
|
|
@@ -34444,6 +34617,7 @@ var missingEffectServiceDependency = createDiagnostic({
|
|
|
34444
34617
|
name: "missingEffectServiceDependency",
|
|
34445
34618
|
code: 22,
|
|
34446
34619
|
description: "Checks that Effect.Service dependencies satisfy all required layer inputs",
|
|
34620
|
+
group: "style",
|
|
34447
34621
|
severity: "off",
|
|
34448
34622
|
fixable: false,
|
|
34449
34623
|
supportedEffect: ["v3"],
|
|
@@ -34540,6 +34714,7 @@ var missingLayerContext = createDiagnostic({
|
|
|
34540
34714
|
name: "missingLayerContext",
|
|
34541
34715
|
code: 38,
|
|
34542
34716
|
description: "Reports missing service requirements in Layer context channel",
|
|
34717
|
+
group: "correctness",
|
|
34543
34718
|
severity: "error",
|
|
34544
34719
|
fixable: false,
|
|
34545
34720
|
supportedEffect: ["v3", "v4"],
|
|
@@ -34591,6 +34766,7 @@ var missingReturnYieldStar = createDiagnostic({
|
|
|
34591
34766
|
name: "missingReturnYieldStar",
|
|
34592
34767
|
code: 7,
|
|
34593
34768
|
description: "Suggests using 'return yield*' for Effects with never success for better type narrowing",
|
|
34769
|
+
group: "correctness",
|
|
34594
34770
|
severity: "error",
|
|
34595
34771
|
fixable: true,
|
|
34596
34772
|
supportedEffect: ["v3", "v4"],
|
|
@@ -34643,6 +34819,7 @@ var missingStarInYieldEffectGen = createDiagnostic({
|
|
|
34643
34819
|
name: "missingStarInYieldEffectGen",
|
|
34644
34820
|
code: 4,
|
|
34645
34821
|
description: "Enforces using 'yield*' instead of 'yield' when yielding Effects in generators",
|
|
34822
|
+
group: "correctness",
|
|
34646
34823
|
severity: "error",
|
|
34647
34824
|
fixable: true,
|
|
34648
34825
|
supportedEffect: ["v3", "v4"],
|
|
@@ -34720,6 +34897,7 @@ var multipleEffectProvide = createDiagnostic({
|
|
|
34720
34897
|
name: "multipleEffectProvide",
|
|
34721
34898
|
code: 18,
|
|
34722
34899
|
description: "Warns against chaining Effect.provide calls which can cause service lifecycle issues",
|
|
34900
|
+
group: "antipattern",
|
|
34723
34901
|
severity: "warning",
|
|
34724
34902
|
fixable: true,
|
|
34725
34903
|
supportedEffect: ["v3", "v4"],
|
|
@@ -34822,7 +35000,11 @@ var moduleAlternativesV3 = /* @__PURE__ */ new Map([
|
|
|
34822
35000
|
["path/win32", { alternative: "Path", module: "path", package: "@effect/platform" }],
|
|
34823
35001
|
["node:path/win32", { alternative: "Path", module: "path", package: "@effect/platform" }],
|
|
34824
35002
|
["child_process", { alternative: "CommandExecutor", module: "child_process", package: "@effect/platform" }],
|
|
34825
|
-
["node:child_process", { alternative: "CommandExecutor", module: "child_process", package: "@effect/platform" }]
|
|
35003
|
+
["node:child_process", { alternative: "CommandExecutor", module: "child_process", package: "@effect/platform" }],
|
|
35004
|
+
["http", { alternative: "HttpClient", module: "http", package: "@effect/platform" }],
|
|
35005
|
+
["node:http", { alternative: "HttpClient", module: "http", package: "@effect/platform" }],
|
|
35006
|
+
["https", { alternative: "HttpClient", module: "https", package: "@effect/platform" }],
|
|
35007
|
+
["node:https", { alternative: "HttpClient", module: "https", package: "@effect/platform" }]
|
|
34826
35008
|
]);
|
|
34827
35009
|
var moduleAlternativesV4 = /* @__PURE__ */ new Map([
|
|
34828
35010
|
["fs", { alternative: "FileSystem", module: "fs", package: "effect" }],
|
|
@@ -34836,12 +35018,17 @@ var moduleAlternativesV4 = /* @__PURE__ */ new Map([
|
|
|
34836
35018
|
["path/win32", { alternative: "Path", module: "path", package: "effect" }],
|
|
34837
35019
|
["node:path/win32", { alternative: "Path", module: "path", package: "effect" }],
|
|
34838
35020
|
["child_process", { alternative: "ChildProcess", module: "child_process", package: "effect" }],
|
|
34839
|
-
["node:child_process", { alternative: "ChildProcess", module: "child_process", package: "effect" }]
|
|
35021
|
+
["node:child_process", { alternative: "ChildProcess", module: "child_process", package: "effect" }],
|
|
35022
|
+
["http", { alternative: "HttpClient", module: "http", package: "effect/unstable/http" }],
|
|
35023
|
+
["node:http", { alternative: "HttpClient", module: "http", package: "effect/unstable/http" }],
|
|
35024
|
+
["https", { alternative: "HttpClient", module: "https", package: "effect/unstable/http" }],
|
|
35025
|
+
["node:https", { alternative: "HttpClient", module: "https", package: "effect/unstable/http" }]
|
|
34840
35026
|
]);
|
|
34841
35027
|
var nodeBuiltinImport = createDiagnostic({
|
|
34842
35028
|
name: "nodeBuiltinImport",
|
|
34843
35029
|
code: 52,
|
|
34844
35030
|
description: "Warns when importing Node.js built-in modules that have Effect-native counterparts",
|
|
35031
|
+
group: "effectNative",
|
|
34845
35032
|
severity: "off",
|
|
34846
35033
|
fixable: false,
|
|
34847
35034
|
supportedEffect: ["v3", "v4"],
|
|
@@ -34885,6 +35072,7 @@ var nonObjectEffectServiceType = createDiagnostic({
|
|
|
34885
35072
|
name: "nonObjectEffectServiceType",
|
|
34886
35073
|
code: 24,
|
|
34887
35074
|
description: "Ensures Effect.Service types are objects, not primitives",
|
|
35075
|
+
group: "correctness",
|
|
34888
35076
|
severity: "error",
|
|
34889
35077
|
fixable: false,
|
|
34890
35078
|
supportedEffect: ["v3"],
|
|
@@ -35669,6 +35857,7 @@ var outdatedApi = createDiagnostic({
|
|
|
35669
35857
|
name: "outdatedApi",
|
|
35670
35858
|
code: 48,
|
|
35671
35859
|
description: "Detects usage of APIs that have been removed or renamed in Effect v4",
|
|
35860
|
+
group: "correctness",
|
|
35672
35861
|
severity: "warning",
|
|
35673
35862
|
fixable: false,
|
|
35674
35863
|
supportedEffect: ["v4"],
|
|
@@ -35738,6 +35927,7 @@ var outdatedEffectCodegen = createDiagnostic({
|
|
|
35738
35927
|
name: "outdatedEffectCodegen",
|
|
35739
35928
|
code: 19,
|
|
35740
35929
|
description: "Detects when generated code is outdated and needs to be regenerated",
|
|
35930
|
+
group: "correctness",
|
|
35741
35931
|
severity: "warning",
|
|
35742
35932
|
fixable: true,
|
|
35743
35933
|
supportedEffect: ["v3", "v4"],
|
|
@@ -35786,6 +35976,7 @@ var overriddenSchemaConstructor = createDiagnostic({
|
|
|
35786
35976
|
name: "overriddenSchemaConstructor",
|
|
35787
35977
|
code: 30,
|
|
35788
35978
|
description: "Prevents overriding constructors in Schema classes which breaks decoding behavior",
|
|
35979
|
+
group: "correctness",
|
|
35789
35980
|
severity: "error",
|
|
35790
35981
|
fixable: true,
|
|
35791
35982
|
supportedEffect: ["v3", "v4"],
|
|
@@ -35925,6 +36116,7 @@ var preferSchemaOverJson = createDiagnostic({
|
|
|
35925
36116
|
name: "preferSchemaOverJson",
|
|
35926
36117
|
code: 44,
|
|
35927
36118
|
description: "Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify which may throw",
|
|
36119
|
+
group: "effectNative",
|
|
35928
36120
|
severity: "suggestion",
|
|
35929
36121
|
fixable: false,
|
|
35930
36122
|
supportedEffect: ["v3", "v4"],
|
|
@@ -36037,6 +36229,7 @@ var redundantSchemaTagIdentifier = createDiagnostic({
|
|
|
36037
36229
|
name: "redundantSchemaTagIdentifier",
|
|
36038
36230
|
code: 42,
|
|
36039
36231
|
description: "Suggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest",
|
|
36232
|
+
group: "style",
|
|
36040
36233
|
severity: "suggestion",
|
|
36041
36234
|
fixable: true,
|
|
36042
36235
|
supportedEffect: ["v3", "v4"],
|
|
@@ -36089,6 +36282,7 @@ var returnEffectInGen = createDiagnostic({
|
|
|
36089
36282
|
name: "returnEffectInGen",
|
|
36090
36283
|
code: 11,
|
|
36091
36284
|
description: "Warns when returning an Effect in a generator causes nested Effect<Effect<...>>",
|
|
36285
|
+
group: "antipattern",
|
|
36092
36286
|
severity: "suggestion",
|
|
36093
36287
|
fixable: true,
|
|
36094
36288
|
supportedEffect: ["v3", "v4"],
|
|
@@ -36160,6 +36354,7 @@ var runEffectInsideEffect = createDiagnostic({
|
|
|
36160
36354
|
name: "runEffectInsideEffect",
|
|
36161
36355
|
code: 32,
|
|
36162
36356
|
description: "Suggests using Runtime methods instead of Effect.run* inside Effect contexts",
|
|
36357
|
+
group: "antipattern",
|
|
36163
36358
|
severity: "suggestion",
|
|
36164
36359
|
fixable: true,
|
|
36165
36360
|
supportedEffect: ["v3"],
|
|
@@ -36286,6 +36481,7 @@ var schemaStructWithTag = createDiagnostic({
|
|
|
36286
36481
|
name: "schemaStructWithTag",
|
|
36287
36482
|
code: 34,
|
|
36288
36483
|
description: "Suggests using Schema.TaggedStruct instead of Schema.Struct with _tag field",
|
|
36484
|
+
group: "style",
|
|
36289
36485
|
severity: "suggestion",
|
|
36290
36486
|
fixable: true,
|
|
36291
36487
|
supportedEffect: ["v3", "v4"],
|
|
@@ -36380,9 +36576,10 @@ var schemaSyncInEffect = createDiagnostic({
|
|
|
36380
36576
|
name: "schemaSyncInEffect",
|
|
36381
36577
|
code: 43,
|
|
36382
36578
|
description: "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
|
|
36579
|
+
group: "antipattern",
|
|
36383
36580
|
severity: "suggestion",
|
|
36384
36581
|
fixable: false,
|
|
36385
|
-
supportedEffect: ["v3"
|
|
36582
|
+
supportedEffect: ["v3"],
|
|
36386
36583
|
apply: fn3("schemaSyncInEffect.apply")(function* (sourceFile, report) {
|
|
36387
36584
|
const ts = yield* service2(TypeScriptApi);
|
|
36388
36585
|
const typeParser = yield* service2(TypeParser);
|
|
@@ -36431,6 +36628,7 @@ var schemaUnionOfLiterals = createDiagnostic({
|
|
|
36431
36628
|
name: "schemaUnionOfLiterals",
|
|
36432
36629
|
code: 33,
|
|
36433
36630
|
description: "Simplifies Schema.Union of multiple Schema.Literal calls into single Schema.Literal",
|
|
36631
|
+
group: "style",
|
|
36434
36632
|
severity: "off",
|
|
36435
36633
|
fixable: true,
|
|
36436
36634
|
supportedEffect: ["v3"],
|
|
@@ -36508,6 +36706,7 @@ var scopeInLayerEffect = createDiagnostic({
|
|
|
36508
36706
|
name: "scopeInLayerEffect",
|
|
36509
36707
|
code: 13,
|
|
36510
36708
|
description: "Suggests using Layer.scoped instead of Layer.effect when Scope is in requirements",
|
|
36709
|
+
group: "antipattern",
|
|
36511
36710
|
severity: "warning",
|
|
36512
36711
|
fixable: true,
|
|
36513
36712
|
supportedEffect: ["v3"],
|
|
@@ -36605,6 +36804,7 @@ var serviceNotAsClass = createDiagnostic({
|
|
|
36605
36804
|
name: "serviceNotAsClass",
|
|
36606
36805
|
code: 51,
|
|
36607
36806
|
description: "Warns when ServiceMap.Service is used as a variable instead of a class declaration",
|
|
36807
|
+
group: "style",
|
|
36608
36808
|
severity: "off",
|
|
36609
36809
|
fixable: true,
|
|
36610
36810
|
supportedEffect: ["v4"],
|
|
@@ -36682,6 +36882,7 @@ var strictBooleanExpressions = createDiagnostic({
|
|
|
36682
36882
|
name: "strictBooleanExpressions",
|
|
36683
36883
|
code: 17,
|
|
36684
36884
|
description: "Enforces boolean types in conditional expressions for type safety",
|
|
36885
|
+
group: "style",
|
|
36685
36886
|
severity: "off",
|
|
36686
36887
|
fixable: false,
|
|
36687
36888
|
supportedEffect: ["v3", "v4"],
|
|
@@ -36755,6 +36956,7 @@ var strictEffectProvide = createDiagnostic({
|
|
|
36755
36956
|
name: "strictEffectProvide",
|
|
36756
36957
|
code: 27,
|
|
36757
36958
|
description: "Warns when using Effect.provide with layers outside of application entry points",
|
|
36959
|
+
group: "antipattern",
|
|
36758
36960
|
severity: "off",
|
|
36759
36961
|
fixable: false,
|
|
36760
36962
|
supportedEffect: ["v3", "v4"],
|
|
@@ -36808,6 +37010,7 @@ var tryCatchInEffectGen = createDiagnostic({
|
|
|
36808
37010
|
name: "tryCatchInEffectGen",
|
|
36809
37011
|
code: 15,
|
|
36810
37012
|
description: "Discourages try/catch in Effect generators in favor of Effect error handling",
|
|
37013
|
+
group: "antipattern",
|
|
36811
37014
|
severity: "suggestion",
|
|
36812
37015
|
fixable: false,
|
|
36813
37016
|
supportedEffect: ["v3", "v4"],
|
|
@@ -36867,6 +37070,7 @@ var unknownInEffectCatch = createDiagnostic({
|
|
|
36867
37070
|
name: "unknownInEffectCatch",
|
|
36868
37071
|
code: 31,
|
|
36869
37072
|
description: "Warns when catch callbacks return unknown instead of typed errors",
|
|
37073
|
+
group: "antipattern",
|
|
36870
37074
|
severity: "warning",
|
|
36871
37075
|
fixable: false,
|
|
36872
37076
|
supportedEffect: ["v3", "v4"],
|
|
@@ -36930,6 +37134,7 @@ var unnecessaryEffectGen = createDiagnostic({
|
|
|
36930
37134
|
name: "unnecessaryEffectGen",
|
|
36931
37135
|
code: 5,
|
|
36932
37136
|
description: "Suggests removing Effect.gen when it contains only a single return statement",
|
|
37137
|
+
group: "style",
|
|
36933
37138
|
severity: "suggestion",
|
|
36934
37139
|
fixable: true,
|
|
36935
37140
|
supportedEffect: ["v3", "v4"],
|
|
@@ -36976,6 +37181,7 @@ var unnecessaryFailYieldableError = createDiagnostic({
|
|
|
36976
37181
|
name: "unnecessaryFailYieldableError",
|
|
36977
37182
|
code: 29,
|
|
36978
37183
|
description: "Suggests yielding yieldable errors directly instead of wrapping with Effect.fail",
|
|
37184
|
+
group: "style",
|
|
36979
37185
|
severity: "suggestion",
|
|
36980
37186
|
fixable: true,
|
|
36981
37187
|
supportedEffect: ["v3", "v4"],
|
|
@@ -37037,6 +37243,7 @@ var unnecessaryPipe = createDiagnostic({
|
|
|
37037
37243
|
name: "unnecessaryPipe",
|
|
37038
37244
|
code: 9,
|
|
37039
37245
|
description: "Removes pipe calls with no arguments",
|
|
37246
|
+
group: "style",
|
|
37040
37247
|
severity: "suggestion",
|
|
37041
37248
|
fixable: true,
|
|
37042
37249
|
supportedEffect: ["v3", "v4"],
|
|
@@ -37085,6 +37292,7 @@ var unnecessaryPipeChain = createDiagnostic({
|
|
|
37085
37292
|
name: "unnecessaryPipeChain",
|
|
37086
37293
|
code: 16,
|
|
37087
37294
|
description: "Simplifies chained pipe calls into a single pipe call",
|
|
37295
|
+
group: "style",
|
|
37088
37296
|
severity: "suggestion",
|
|
37089
37297
|
fixable: true,
|
|
37090
37298
|
supportedEffect: ["v3", "v4"],
|
|
@@ -37162,6 +37370,7 @@ var unsupportedServiceAccessors = createDiagnostic({
|
|
|
37162
37370
|
name: "unsupportedServiceAccessors",
|
|
37163
37371
|
code: 21,
|
|
37164
37372
|
description: "Warns about service accessors that need codegen due to generic/complex signatures",
|
|
37373
|
+
group: "correctness",
|
|
37165
37374
|
severity: "warning",
|
|
37166
37375
|
fixable: true,
|
|
37167
37376
|
supportedEffect: ["v3", "v4"],
|
|
@@ -37225,6 +37434,7 @@ var diagnostics = [
|
|
|
37225
37434
|
catchUnfailableEffect,
|
|
37226
37435
|
classSelfMismatch,
|
|
37227
37436
|
duplicatePackage,
|
|
37437
|
+
effectFnImplicitAny,
|
|
37228
37438
|
effectGenUsesAdapter,
|
|
37229
37439
|
missingEffectContext,
|
|
37230
37440
|
missingEffectError,
|
|
@@ -37239,6 +37449,7 @@ var diagnostics = [
|
|
|
37239
37449
|
leakingRequirements,
|
|
37240
37450
|
unnecessaryPipe,
|
|
37241
37451
|
genericEffectServices,
|
|
37452
|
+
globalFetch,
|
|
37242
37453
|
returnEffectInGen,
|
|
37243
37454
|
tryCatchInEffectGen,
|
|
37244
37455
|
importFromBarrel,
|
|
@@ -37307,6 +37518,8 @@ var formatDiagnosticForJson = (diagnostic, tsInstance) => {
|
|
|
37307
37518
|
const diagnosticName = Object.values(diagnostics).find((_) => _.code === diagnostic.code)?.name ?? `effect(${diagnostic.code})`;
|
|
37308
37519
|
return {
|
|
37309
37520
|
file: diagnostic.file.fileName,
|
|
37521
|
+
start: diagnostic.start,
|
|
37522
|
+
length: diagnostic.length ?? 0,
|
|
37310
37523
|
line: line + 1,
|
|
37311
37524
|
column: character + 1,
|
|
37312
37525
|
endLine: endLine + 1,
|
|
@@ -38201,13 +38414,6 @@ var GREEN = "\x1B[0;32m";
|
|
|
38201
38414
|
var YELLOW = "\x1B[0;33m";
|
|
38202
38415
|
var BLUE = "\x1B[0;34m";
|
|
38203
38416
|
var CYAN = "\x1B[0;36m";
|
|
38204
|
-
var WHITE = "\x1B[0;37m";
|
|
38205
|
-
var CYAN_BRIGHT = "\x1B[0;96m";
|
|
38206
|
-
var BG_BLACK_BRIGHT = "\x1B[0;100m";
|
|
38207
|
-
var BG_RED = "\x1B[41m";
|
|
38208
|
-
var BG_YELLOW = "\x1B[43m";
|
|
38209
|
-
var BG_BLUE = "\x1B[0;44m";
|
|
38210
|
-
var BG_CYAN = "\x1B[0;46m";
|
|
38211
38417
|
var ansi = (text2, code) => `${code}${text2}${RESET}`;
|
|
38212
38418
|
var ERASE_LINE = "\x1B[2K";
|
|
38213
38419
|
var CURSOR_LEFT = "\r";
|
|
@@ -38215,6 +38421,11 @@ var CURSOR_HIDE = "\x1B[?25l";
|
|
|
38215
38421
|
var CURSOR_SHOW = "\x1B[?25h";
|
|
38216
38422
|
var CURSOR_TO_0 = "\x1B[G";
|
|
38217
38423
|
var BEEP = "\x07";
|
|
38424
|
+
var ITALIC = "\x1B[0;3m";
|
|
38425
|
+
var UNDERLINE = "\x1B[0;4m";
|
|
38426
|
+
var ANSI_ESCAPE_REGEX = new RegExp(String.raw`\u001b\[[0-?]*[ -/]*[@-~]`, "g");
|
|
38427
|
+
var stripAnsi2 = (text2) => text2.replace(ANSI_ESCAPE_REGEX, "");
|
|
38428
|
+
var visibleLength = (text2) => stripAnsi2(text2).length;
|
|
38218
38429
|
|
|
38219
38430
|
// src/cli/overview.ts
|
|
38220
38431
|
var import_project_service3 = __toESM(require_dist3());
|
|
@@ -40178,9 +40389,100 @@ var computeTsConfigChanges = (current, target, lspVersion) => {
|
|
|
40178
40389
|
if (!rootObj) {
|
|
40179
40390
|
return emptyFileChangesResult();
|
|
40180
40391
|
}
|
|
40392
|
+
const buildPluginObject = (severities) => {
|
|
40393
|
+
const nameProperty = ts.factory.createPropertyAssignment(
|
|
40394
|
+
ts.factory.createStringLiteral("name"),
|
|
40395
|
+
ts.factory.createStringLiteral("@effect/language-service")
|
|
40396
|
+
);
|
|
40397
|
+
return match(severities, {
|
|
40398
|
+
onNone: () => {
|
|
40399
|
+
return ts.factory.createObjectLiteralExpression([nameProperty], false);
|
|
40400
|
+
},
|
|
40401
|
+
onSome: (sevs) => {
|
|
40402
|
+
const severityProperties = Object.entries(sevs).map(
|
|
40403
|
+
([name, severity]) => ts.factory.createPropertyAssignment(
|
|
40404
|
+
ts.factory.createStringLiteral(name),
|
|
40405
|
+
ts.factory.createStringLiteral(severity)
|
|
40406
|
+
)
|
|
40407
|
+
);
|
|
40408
|
+
const diagnosticSeverityProperty = ts.factory.createPropertyAssignment(
|
|
40409
|
+
ts.factory.createStringLiteral("diagnosticSeverity"),
|
|
40410
|
+
ts.factory.createObjectLiteralExpression(severityProperties, true)
|
|
40411
|
+
);
|
|
40412
|
+
return ts.factory.createObjectLiteralExpression(
|
|
40413
|
+
[nameProperty, diagnosticSeverityProperty],
|
|
40414
|
+
true
|
|
40415
|
+
);
|
|
40416
|
+
}
|
|
40417
|
+
});
|
|
40418
|
+
};
|
|
40419
|
+
const schemaPropertyAssignment = ts.factory.createPropertyAssignment(
|
|
40420
|
+
ts.factory.createStringLiteral("$schema"),
|
|
40421
|
+
ts.factory.createStringLiteral(TSCONFIG_SCHEMA_URL)
|
|
40422
|
+
);
|
|
40181
40423
|
const compilerOptionsProperty = findPropertyInObject(ts, rootObj, "compilerOptions");
|
|
40182
40424
|
if (!compilerOptionsProperty) {
|
|
40183
|
-
|
|
40425
|
+
if (isNone2(lspVersion)) {
|
|
40426
|
+
return emptyFileChangesResult();
|
|
40427
|
+
}
|
|
40428
|
+
const textChanges2 = ts.textChanges;
|
|
40429
|
+
const host2 = createMinimalHost(ts);
|
|
40430
|
+
const formatOptions2 = { indentSize: 2, tabSize: 2 };
|
|
40431
|
+
const formatContext2 = ts.formatting.getFormatContext(formatOptions2, host2);
|
|
40432
|
+
const preferences2 = {};
|
|
40433
|
+
const fileChanges2 = textChanges2.ChangeTracker.with(
|
|
40434
|
+
{ host: host2, formatContext: formatContext2, preferences: preferences2 },
|
|
40435
|
+
(tracker) => {
|
|
40436
|
+
const schemaProperty = findPropertyInObject(ts, rootObj, "$schema");
|
|
40437
|
+
const shouldAddSchema = !schemaProperty;
|
|
40438
|
+
const shouldUpdateSchema = !!schemaProperty && (!ts.isStringLiteral(schemaProperty.initializer) || schemaProperty.initializer.text !== TSCONFIG_SCHEMA_URL);
|
|
40439
|
+
if (shouldAddSchema) {
|
|
40440
|
+
descriptions.push("Add $schema to tsconfig");
|
|
40441
|
+
} else if (shouldUpdateSchema) {
|
|
40442
|
+
descriptions.push("Update $schema in tsconfig");
|
|
40443
|
+
}
|
|
40444
|
+
descriptions.push("Add compilerOptions with @effect/language-service plugin");
|
|
40445
|
+
const compilerOptionsAssignment = ts.factory.createPropertyAssignment(
|
|
40446
|
+
ts.factory.createStringLiteral("compilerOptions"),
|
|
40447
|
+
ts.factory.createObjectLiteralExpression([
|
|
40448
|
+
ts.factory.createPropertyAssignment(
|
|
40449
|
+
ts.factory.createStringLiteral("plugins"),
|
|
40450
|
+
ts.factory.createArrayLiteralExpression([buildPluginObject(target.diagnosticSeverities)], true)
|
|
40451
|
+
)
|
|
40452
|
+
], true)
|
|
40453
|
+
);
|
|
40454
|
+
const nextProperties = rootObj.properties.flatMap((property) => {
|
|
40455
|
+
if (schemaProperty && property === schemaProperty) {
|
|
40456
|
+
return [schemaPropertyAssignment];
|
|
40457
|
+
}
|
|
40458
|
+
return [property];
|
|
40459
|
+
});
|
|
40460
|
+
if (shouldAddSchema) {
|
|
40461
|
+
nextProperties.push(schemaPropertyAssignment);
|
|
40462
|
+
}
|
|
40463
|
+
nextProperties.push(compilerOptionsAssignment);
|
|
40464
|
+
tracker.replaceNode(
|
|
40465
|
+
current.sourceFile,
|
|
40466
|
+
rootObj,
|
|
40467
|
+
ts.factory.createObjectLiteralExpression(nextProperties, true)
|
|
40468
|
+
);
|
|
40469
|
+
}
|
|
40470
|
+
);
|
|
40471
|
+
const fileChange2 = fileChanges2.find((fc) => fc.fileName === current.path);
|
|
40472
|
+
const changes2 = fileChange2 ? fileChange2.textChanges : [];
|
|
40473
|
+
if (changes2.length === 0) {
|
|
40474
|
+
return { codeActions: [], messages };
|
|
40475
|
+
}
|
|
40476
|
+
return {
|
|
40477
|
+
codeActions: [{
|
|
40478
|
+
description: descriptions.join("; "),
|
|
40479
|
+
changes: [{
|
|
40480
|
+
fileName: current.path,
|
|
40481
|
+
textChanges: changes2
|
|
40482
|
+
}]
|
|
40483
|
+
}],
|
|
40484
|
+
messages
|
|
40485
|
+
};
|
|
40184
40486
|
}
|
|
40185
40487
|
if (!ts.isObjectLiteralExpression(compilerOptionsProperty.initializer)) {
|
|
40186
40488
|
return emptyFileChangesResult();
|
|
@@ -40196,10 +40498,6 @@ var computeTsConfigChanges = (current, target, lspVersion) => {
|
|
|
40196
40498
|
(tracker) => {
|
|
40197
40499
|
const schemaProperty = findPropertyInObject(ts, rootObj, "$schema");
|
|
40198
40500
|
const pluginsProperty = findPropertyInObject(ts, compilerOptions, "plugins");
|
|
40199
|
-
const schemaPropertyAssignment = ts.factory.createPropertyAssignment(
|
|
40200
|
-
ts.factory.createStringLiteral("$schema"),
|
|
40201
|
-
ts.factory.createStringLiteral(TSCONFIG_SCHEMA_URL)
|
|
40202
|
-
);
|
|
40203
40501
|
if (isNone2(lspVersion)) {
|
|
40204
40502
|
if (schemaProperty) {
|
|
40205
40503
|
descriptions.push("Remove $schema from tsconfig");
|
|
@@ -40229,33 +40527,6 @@ var computeTsConfigChanges = (current, target, lspVersion) => {
|
|
|
40229
40527
|
descriptions.push("Update $schema in tsconfig");
|
|
40230
40528
|
tracker.replaceNode(current.sourceFile, schemaProperty.initializer, schemaPropertyAssignment.initializer);
|
|
40231
40529
|
}
|
|
40232
|
-
const buildPluginObject = (severities) => {
|
|
40233
|
-
const nameProperty = ts.factory.createPropertyAssignment(
|
|
40234
|
-
ts.factory.createStringLiteral("name"),
|
|
40235
|
-
ts.factory.createStringLiteral("@effect/language-service")
|
|
40236
|
-
);
|
|
40237
|
-
return match(severities, {
|
|
40238
|
-
onNone: () => {
|
|
40239
|
-
return ts.factory.createObjectLiteralExpression([nameProperty], false);
|
|
40240
|
-
},
|
|
40241
|
-
onSome: (sevs) => {
|
|
40242
|
-
const severityProperties = Object.entries(sevs).map(
|
|
40243
|
-
([name, severity]) => ts.factory.createPropertyAssignment(
|
|
40244
|
-
ts.factory.createStringLiteral(name),
|
|
40245
|
-
ts.factory.createStringLiteral(severity)
|
|
40246
|
-
)
|
|
40247
|
-
);
|
|
40248
|
-
const diagnosticSeverityProperty = ts.factory.createPropertyAssignment(
|
|
40249
|
-
ts.factory.createStringLiteral("diagnosticSeverity"),
|
|
40250
|
-
ts.factory.createObjectLiteralExpression(severityProperties, true)
|
|
40251
|
-
);
|
|
40252
|
-
return ts.factory.createObjectLiteralExpression(
|
|
40253
|
-
[nameProperty, diagnosticSeverityProperty],
|
|
40254
|
-
true
|
|
40255
|
-
);
|
|
40256
|
-
}
|
|
40257
|
-
});
|
|
40258
|
-
};
|
|
40259
40530
|
const pluginObject = buildPluginObject(target.diagnosticSeverities);
|
|
40260
40531
|
if (!pluginsProperty) {
|
|
40261
40532
|
descriptions.push("Add plugins array with @effect/language-service plugin");
|
|
@@ -40468,244 +40739,1677 @@ var FileReadError = class extends TaggedError2("FileReadError") {
|
|
|
40468
40739
|
}
|
|
40469
40740
|
};
|
|
40470
40741
|
|
|
40471
|
-
// src/
|
|
40472
|
-
|
|
40473
|
-
|
|
40474
|
-
|
|
40475
|
-
|
|
40476
|
-
|
|
40477
|
-
|
|
40478
|
-
|
|
40479
|
-
|
|
40480
|
-
|
|
40481
|
-
|
|
40482
|
-
|
|
40483
|
-
|
|
40484
|
-
|
|
40485
|
-
|
|
40486
|
-
|
|
40487
|
-
|
|
40488
|
-
}
|
|
40489
|
-
|
|
40490
|
-
|
|
40491
|
-
|
|
40492
|
-
|
|
40493
|
-
warning: "warn",
|
|
40494
|
-
error: "err"
|
|
40495
|
-
};
|
|
40496
|
-
var MAX_SEVERITY_LENGTH = Object.values(shortNames).reduce((max2, name) => Math.max(max2, name.length), 0);
|
|
40497
|
-
function getSeverityShortName(severity) {
|
|
40498
|
-
return shortNames[severity] ?? "???";
|
|
40499
|
-
}
|
|
40500
|
-
|
|
40501
|
-
// src/cli/setup/diagnostic-prompt.ts
|
|
40502
|
-
function eraseLines2(count) {
|
|
40503
|
-
let result3 = "";
|
|
40504
|
-
for (let i = 0; i < count; i++) {
|
|
40505
|
-
if (i > 0) result3 += "\x1B[1A";
|
|
40506
|
-
result3 += ERASE_LINE;
|
|
40507
|
-
}
|
|
40508
|
-
if (count > 0) result3 += CURSOR_LEFT;
|
|
40509
|
-
return result3;
|
|
40510
|
-
}
|
|
40511
|
-
var Action2 = taggedEnum();
|
|
40512
|
-
var NEWLINE_REGEX = /\r?\n/;
|
|
40513
|
-
function eraseText2(text2, columns) {
|
|
40514
|
-
if (columns === 0) {
|
|
40515
|
-
return ERASE_LINE + CURSOR_TO_0;
|
|
40516
|
-
}
|
|
40517
|
-
let rows = 0;
|
|
40518
|
-
const lines2 = text2.split(/\r?\n/);
|
|
40519
|
-
for (const line of lines2) {
|
|
40520
|
-
rows += 1 + Math.floor(Math.max(line.length - 1, 0) / columns);
|
|
40521
|
-
}
|
|
40522
|
-
return eraseLines2(rows);
|
|
40523
|
-
}
|
|
40524
|
-
function entriesToDisplay2(cursor, total, maxVisible) {
|
|
40525
|
-
const max2 = maxVisible === void 0 ? total : maxVisible;
|
|
40526
|
-
let startIndex = Math.min(total - max2, cursor - Math.floor(max2 / 2));
|
|
40527
|
-
if (startIndex < 0) {
|
|
40528
|
-
startIndex = 0;
|
|
40529
|
-
}
|
|
40530
|
-
const endIndex = Math.min(startIndex + max2, total);
|
|
40531
|
-
return { startIndex, endIndex };
|
|
40532
|
-
}
|
|
40533
|
-
var defaultFigures2 = {
|
|
40534
|
-
arrowUp: "\u2191",
|
|
40535
|
-
arrowDown: "\u2193",
|
|
40536
|
-
tick: "\u2714",
|
|
40537
|
-
pointerSmall: "\u203A"
|
|
40538
|
-
};
|
|
40539
|
-
var figuresValue = defaultFigures2;
|
|
40540
|
-
function getSeverityStyle(severity) {
|
|
40541
|
-
const styles = {
|
|
40542
|
-
off: WHITE + BG_BLACK_BRIGHT,
|
|
40543
|
-
suggestion: WHITE + BG_CYAN,
|
|
40544
|
-
message: WHITE + BG_BLUE,
|
|
40545
|
-
warning: WHITE + BG_YELLOW,
|
|
40546
|
-
error: WHITE + BG_RED
|
|
40547
|
-
};
|
|
40548
|
-
return styles[severity];
|
|
40549
|
-
}
|
|
40550
|
-
function renderOutput(leadingSymbol, trailingSymbol, options) {
|
|
40551
|
-
const annotateLine2 = (line) => ansi(line, BOLD);
|
|
40552
|
-
const prefix = leadingSymbol + " ";
|
|
40553
|
-
return match3(options.message.split(NEWLINE_REGEX), {
|
|
40554
|
-
onEmpty: () => `${prefix}${trailingSymbol}`,
|
|
40555
|
-
onNonEmpty: (promptLines) => {
|
|
40556
|
-
const lines2 = map4(promptLines, (line) => annotateLine2(line));
|
|
40557
|
-
return `${prefix}${lines2.join("\n ")} ${trailingSymbol} `;
|
|
40558
|
-
}
|
|
40559
|
-
});
|
|
40560
|
-
}
|
|
40561
|
-
function renderDiagnostics(state, options, figs, columns) {
|
|
40562
|
-
const diagnostics3 = options.diagnostics;
|
|
40563
|
-
const toDisplay = entriesToDisplay2(state.index, diagnostics3.length, options.maxPerPage);
|
|
40564
|
-
const documents = [];
|
|
40565
|
-
for (let index = toDisplay.startIndex; index < toDisplay.endIndex; index++) {
|
|
40566
|
-
const diagnostic = diagnostics3[index];
|
|
40567
|
-
const isHighlighted = state.index === index;
|
|
40568
|
-
const currentSeverity = state.severities[diagnostic.name] ?? diagnostic.defaultSeverity;
|
|
40569
|
-
const hasChanged = currentSeverity !== diagnostic.defaultSeverity;
|
|
40570
|
-
let prefix = " ";
|
|
40571
|
-
if (index === toDisplay.startIndex && toDisplay.startIndex > 0) {
|
|
40572
|
-
prefix = figs.arrowUp;
|
|
40573
|
-
} else if (index === toDisplay.endIndex - 1 && toDisplay.endIndex < diagnostics3.length) {
|
|
40574
|
-
prefix = figs.arrowDown;
|
|
40575
|
-
}
|
|
40576
|
-
const shortName = getSeverityShortName(currentSeverity);
|
|
40577
|
-
const paddedSeverity = shortName.padEnd(MAX_SEVERITY_LENGTH, " ");
|
|
40578
|
-
const severityStr = ansi(` ${paddedSeverity} `, getSeverityStyle(currentSeverity));
|
|
40579
|
-
const nameText = hasChanged ? `${diagnostic.name}*` : diagnostic.name;
|
|
40580
|
-
const nameStr = isHighlighted ? ansi(nameText, CYAN_BRIGHT) : nameText;
|
|
40581
|
-
const mainLine = `${prefix} ${severityStr} ${nameStr}`;
|
|
40582
|
-
if (isHighlighted && diagnostic.description) {
|
|
40583
|
-
const indentWidth = 1 + 1 + (MAX_SEVERITY_LENGTH + 2) + 1;
|
|
40584
|
-
const indent = " ".repeat(indentWidth);
|
|
40585
|
-
const availableWidth = columns - indentWidth;
|
|
40586
|
-
const truncatedDescription = availableWidth > 0 && diagnostic.description.length > availableWidth ? diagnostic.description.substring(0, availableWidth - 1) + "\u2026" : diagnostic.description;
|
|
40587
|
-
const descriptionStr = ansi(indent + truncatedDescription, DIM);
|
|
40588
|
-
documents.push(mainLine + "\n" + descriptionStr);
|
|
40589
|
-
} else {
|
|
40590
|
-
documents.push(mainLine);
|
|
40591
|
-
}
|
|
40592
|
-
}
|
|
40593
|
-
return documents.join("\n");
|
|
40594
|
-
}
|
|
40595
|
-
function renderNextFrame(state, options) {
|
|
40596
|
-
return gen2(function* () {
|
|
40597
|
-
const terminal = yield* Terminal;
|
|
40598
|
-
const columns = yield* terminal.columns;
|
|
40599
|
-
const figs = figuresValue;
|
|
40600
|
-
const diagnosticsStr = renderDiagnostics(state, options, figs, columns);
|
|
40601
|
-
const leadingSymbol = ansi("?", CYAN_BRIGHT);
|
|
40602
|
-
const trailingSymbol = figs.pointerSmall;
|
|
40603
|
-
const promptMsg = renderOutput(leadingSymbol, trailingSymbol, options);
|
|
40604
|
-
const helpText = ansi(
|
|
40605
|
-
"Use \u2191/\u2193 to navigate, \u2190/\u2192 to change severity, Enter to finish",
|
|
40606
|
-
DIM
|
|
40607
|
-
);
|
|
40608
|
-
return CURSOR_HIDE + promptMsg + "\n" + helpText + "\n" + diagnosticsStr;
|
|
40609
|
-
});
|
|
40610
|
-
}
|
|
40611
|
-
function renderSubmission(state, options) {
|
|
40612
|
-
return gen2(function* () {
|
|
40613
|
-
const figs = figuresValue;
|
|
40614
|
-
const changedCount = Object.entries(state.severities).filter(([name, severity]) => {
|
|
40615
|
-
const diagnostic = options.diagnostics.find((d) => d.name === name);
|
|
40616
|
-
return diagnostic && severity !== diagnostic.defaultSeverity;
|
|
40617
|
-
}).length;
|
|
40618
|
-
const result3 = ansi(
|
|
40619
|
-
`${changedCount} diagnostic${changedCount === 1 ? "" : "s"} configured`,
|
|
40620
|
-
WHITE
|
|
40621
|
-
);
|
|
40622
|
-
const leadingSymbol = ansi(figs.tick, GREEN);
|
|
40623
|
-
const trailingSymbol = "";
|
|
40624
|
-
const promptMsg = renderOutput(leadingSymbol, trailingSymbol, options);
|
|
40625
|
-
return promptMsg + " " + result3 + "\n";
|
|
40626
|
-
});
|
|
40627
|
-
}
|
|
40628
|
-
function processCursorUp(state, totalCount) {
|
|
40629
|
-
const newIndex = state.index === 0 ? totalCount - 1 : state.index - 1;
|
|
40630
|
-
return succeed6(Action2.NextFrame({ state: { ...state, index: newIndex } }));
|
|
40631
|
-
}
|
|
40632
|
-
function processCursorDown(state, totalCount) {
|
|
40633
|
-
const newIndex = (state.index + 1) % totalCount;
|
|
40634
|
-
return succeed6(Action2.NextFrame({ state: { ...state, index: newIndex } }));
|
|
40635
|
-
}
|
|
40636
|
-
function processSeverityChange(state, options, direction) {
|
|
40637
|
-
const diagnostic = options.diagnostics[state.index];
|
|
40638
|
-
const currentSeverity = state.severities[diagnostic.name] ?? diagnostic.defaultSeverity;
|
|
40639
|
-
const newSeverity = cycleSeverity(currentSeverity, direction);
|
|
40640
|
-
return succeed6(Action2.NextFrame({
|
|
40641
|
-
state: {
|
|
40642
|
-
...state,
|
|
40643
|
-
severities: { ...state.severities, [diagnostic.name]: newSeverity }
|
|
40742
|
+
// src/metadata.json
|
|
40743
|
+
var metadata_default = {
|
|
40744
|
+
groups: [
|
|
40745
|
+
{
|
|
40746
|
+
id: "correctness",
|
|
40747
|
+
name: "Correctness",
|
|
40748
|
+
description: "Wrong, unsafe, or structurally invalid code patterns."
|
|
40749
|
+
},
|
|
40750
|
+
{
|
|
40751
|
+
id: "antipattern",
|
|
40752
|
+
name: "Anti-pattern",
|
|
40753
|
+
description: "Discouraged patterns that often lead to bugs or confusing behavior."
|
|
40754
|
+
},
|
|
40755
|
+
{
|
|
40756
|
+
id: "effectNative",
|
|
40757
|
+
name: "Effect-native",
|
|
40758
|
+
description: "Prefer Effect-native APIs and abstractions when available."
|
|
40759
|
+
},
|
|
40760
|
+
{
|
|
40761
|
+
id: "style",
|
|
40762
|
+
name: "Style",
|
|
40763
|
+
description: "Cleanup, consistency, and idiomatic Effect code."
|
|
40644
40764
|
}
|
|
40645
|
-
|
|
40646
|
-
|
|
40647
|
-
|
|
40648
|
-
|
|
40649
|
-
|
|
40650
|
-
|
|
40651
|
-
|
|
40652
|
-
|
|
40653
|
-
|
|
40654
|
-
|
|
40655
|
-
|
|
40656
|
-
|
|
40657
|
-
|
|
40765
|
+
],
|
|
40766
|
+
rules: [
|
|
40767
|
+
{
|
|
40768
|
+
name: "anyUnknownInErrorContext",
|
|
40769
|
+
group: "correctness",
|
|
40770
|
+
description: "Detects 'any' or 'unknown' types in Effect error or requirements channels",
|
|
40771
|
+
defaultSeverity: "off",
|
|
40772
|
+
fixable: false,
|
|
40773
|
+
supportedEffect: [
|
|
40774
|
+
"v3",
|
|
40775
|
+
"v4"
|
|
40776
|
+
],
|
|
40777
|
+
preview: {
|
|
40778
|
+
sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.gen(function*() {\n yield* Effect.services<unknown>()\n return yield* Effect.fail<any>("boom")\n})\n',
|
|
40779
|
+
diagnostics: [
|
|
40780
|
+
{
|
|
40781
|
+
start: 46,
|
|
40782
|
+
end: 53,
|
|
40783
|
+
text: "This has unknown in the requirements channel and any in the error channel which is not recommended.\nOnly service identifiers should appear in the requirements channel.\nHaving an unknown or any error type is not useful. Consider instead using specific error types baked by Data.TaggedError for example. effect(anyUnknownInErrorContext)"
|
|
40784
|
+
},
|
|
40785
|
+
{
|
|
40786
|
+
start: 90,
|
|
40787
|
+
end: 116,
|
|
40788
|
+
text: "This has unknown in the requirements channel which is not recommended.\nOnly service identifiers should appear in the requirements channel. effect(anyUnknownInErrorContext)"
|
|
40789
|
+
},
|
|
40790
|
+
{
|
|
40791
|
+
start: 133,
|
|
40792
|
+
end: 157,
|
|
40793
|
+
text: "This has any in the error channel which is not recommended.\nHaving an unknown or any error type is not useful. Consider instead using specific error types baked by Data.TaggedError for example. effect(anyUnknownInErrorContext)"
|
|
40794
|
+
}
|
|
40795
|
+
]
|
|
40658
40796
|
}
|
|
40659
|
-
|
|
40660
|
-
|
|
40797
|
+
},
|
|
40798
|
+
{
|
|
40799
|
+
name: "classSelfMismatch",
|
|
40800
|
+
group: "correctness",
|
|
40801
|
+
description: "Ensures Self type parameter matches the class name in Service/Tag/Schema classes",
|
|
40802
|
+
defaultSeverity: "error",
|
|
40803
|
+
fixable: true,
|
|
40804
|
+
supportedEffect: [
|
|
40805
|
+
"v3",
|
|
40806
|
+
"v4"
|
|
40807
|
+
],
|
|
40808
|
+
preview: {
|
|
40809
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\ninterface ServiceShape { value: number }\n\nexport class InvalidContextTag\n extends Effect.Tag("ValidContextTag")<ValidContextTag, ServiceShape>() {}\n\ndeclare class ValidContextTag {}\n',
|
|
40810
|
+
diagnostics: [
|
|
40811
|
+
{
|
|
40812
|
+
start: 154,
|
|
40813
|
+
end: 169,
|
|
40814
|
+
text: "Self type parameter should be 'InvalidContextTag' effect(classSelfMismatch)"
|
|
40815
|
+
}
|
|
40816
|
+
]
|
|
40661
40817
|
}
|
|
40662
|
-
|
|
40663
|
-
|
|
40818
|
+
},
|
|
40819
|
+
{
|
|
40820
|
+
name: "duplicatePackage",
|
|
40821
|
+
group: "correctness",
|
|
40822
|
+
description: "Detects when multiple versions of the same Effect package are loaded",
|
|
40823
|
+
defaultSeverity: "warning",
|
|
40824
|
+
fixable: false,
|
|
40825
|
+
supportedEffect: [
|
|
40826
|
+
"v3",
|
|
40827
|
+
"v4"
|
|
40828
|
+
],
|
|
40829
|
+
preview: {
|
|
40830
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\n// This preview only reports when the workspace resolves duplicated\n// Effect package versions.\nexport const preview = Effect.succeed(true)\n',
|
|
40831
|
+
diagnostics: []
|
|
40664
40832
|
}
|
|
40665
|
-
|
|
40666
|
-
|
|
40667
|
-
|
|
40833
|
+
},
|
|
40834
|
+
{
|
|
40835
|
+
name: "effectFnImplicitAny",
|
|
40836
|
+
group: "correctness",
|
|
40837
|
+
description: "Mirrors noImplicitAny for unannotated Effect.fn and Effect.fnUntraced callback parameters when no outer contextual function type exists",
|
|
40838
|
+
defaultSeverity: "error",
|
|
40839
|
+
fixable: false,
|
|
40840
|
+
supportedEffect: [
|
|
40841
|
+
"v3",
|
|
40842
|
+
"v4"
|
|
40843
|
+
],
|
|
40844
|
+
preview: {
|
|
40845
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.fn("preview")((input) => Effect.succeed(input))\n',
|
|
40846
|
+
diagnostics: [
|
|
40847
|
+
{
|
|
40848
|
+
start: 86,
|
|
40849
|
+
end: 91,
|
|
40850
|
+
text: "Parameter 'input' implicitly has an 'any' type in Effect.fn/Effect.fnUntraced. Add an explicit type annotation or provide a contextual function type. effect(effectFnImplicitAny)"
|
|
40851
|
+
}
|
|
40852
|
+
]
|
|
40668
40853
|
}
|
|
40669
|
-
|
|
40670
|
-
|
|
40854
|
+
},
|
|
40855
|
+
{
|
|
40856
|
+
name: "floatingEffect",
|
|
40857
|
+
group: "correctness",
|
|
40858
|
+
description: "Ensures Effects are yielded or assigned to variables, not left floating",
|
|
40859
|
+
defaultSeverity: "error",
|
|
40860
|
+
fixable: false,
|
|
40861
|
+
supportedEffect: [
|
|
40862
|
+
"v3",
|
|
40863
|
+
"v4"
|
|
40864
|
+
],
|
|
40865
|
+
preview: {
|
|
40866
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nEffect.log("forgotten")\n',
|
|
40867
|
+
diagnostics: [
|
|
40868
|
+
{
|
|
40869
|
+
start: 41,
|
|
40870
|
+
end: 64,
|
|
40871
|
+
text: "Effect must be yielded or assigned to a variable. effect(floatingEffect)"
|
|
40872
|
+
}
|
|
40873
|
+
]
|
|
40671
40874
|
}
|
|
40672
|
-
}
|
|
40673
|
-
|
|
40875
|
+
},
|
|
40876
|
+
{
|
|
40877
|
+
name: "genericEffectServices",
|
|
40878
|
+
group: "correctness",
|
|
40879
|
+
description: "Prevents services with type parameters that cannot be discriminated at runtime",
|
|
40880
|
+
defaultSeverity: "warning",
|
|
40881
|
+
fixable: false,
|
|
40882
|
+
supportedEffect: [
|
|
40883
|
+
"v3",
|
|
40884
|
+
"v4"
|
|
40885
|
+
],
|
|
40886
|
+
preview: {
|
|
40887
|
+
sourceText: 'import { Effect } from "effect"\n\nexport class Preview<_A> extends Effect.Service<Preview<any>>()("Preview", {\n succeed: {}\n}) {}\n',
|
|
40888
|
+
diagnostics: [
|
|
40889
|
+
{
|
|
40890
|
+
start: 46,
|
|
40891
|
+
end: 53,
|
|
40892
|
+
text: "Effect Services with type parameters are not supported because they cannot be properly discriminated at runtime, which may cause unexpected behavior. effect(genericEffectServices)"
|
|
40893
|
+
}
|
|
40894
|
+
]
|
|
40895
|
+
}
|
|
40896
|
+
},
|
|
40897
|
+
{
|
|
40898
|
+
name: "missingEffectContext",
|
|
40899
|
+
group: "correctness",
|
|
40900
|
+
description: "Reports missing service requirements in Effect context channel",
|
|
40901
|
+
defaultSeverity: "error",
|
|
40902
|
+
fixable: false,
|
|
40903
|
+
supportedEffect: [
|
|
40904
|
+
"v3",
|
|
40905
|
+
"v4"
|
|
40906
|
+
],
|
|
40907
|
+
preview: {
|
|
40908
|
+
sourceText: 'import { Effect, ServiceMap } from "effect"\n\nclass Db extends ServiceMap.Service<Db>()("Db", { make: Effect.succeed({}) }) {}\n\n// @ts-expect-error\nexport const preview: Effect.Effect<void> = Db.asEffect().pipe(Effect.asVoid)\n',
|
|
40909
|
+
diagnostics: [
|
|
40910
|
+
{
|
|
40911
|
+
start: 160,
|
|
40912
|
+
end: 167,
|
|
40913
|
+
text: "Missing 'Db' in the expected Effect context. effect(missingEffectContext)"
|
|
40914
|
+
}
|
|
40915
|
+
]
|
|
40916
|
+
}
|
|
40917
|
+
},
|
|
40918
|
+
{
|
|
40919
|
+
name: "missingEffectError",
|
|
40920
|
+
group: "correctness",
|
|
40921
|
+
description: "Reports missing error types in Effect error channel",
|
|
40922
|
+
defaultSeverity: "error",
|
|
40923
|
+
fixable: true,
|
|
40924
|
+
supportedEffect: [
|
|
40925
|
+
"v3",
|
|
40926
|
+
"v4"
|
|
40927
|
+
],
|
|
40928
|
+
preview: {
|
|
40929
|
+
sourceText: 'import type * as Effect from "effect/Effect"\nimport { Data } from "effect"\n\nclass Boom extends Data.TaggedError("Boom")<{}> {}\ndeclare const effect: Effect.Effect<number, Boom>\n\n// @ts-expect-error\nexport const preview: () => Effect.Effect<number> = () => effect\n',
|
|
40930
|
+
diagnostics: [
|
|
40931
|
+
{
|
|
40932
|
+
start: 256,
|
|
40933
|
+
end: 262,
|
|
40934
|
+
text: "Missing 'Boom' in the expected Effect errors. effect(missingEffectError)"
|
|
40935
|
+
}
|
|
40936
|
+
]
|
|
40937
|
+
}
|
|
40938
|
+
},
|
|
40939
|
+
{
|
|
40940
|
+
name: "missingLayerContext",
|
|
40941
|
+
group: "correctness",
|
|
40942
|
+
description: "Reports missing service requirements in Layer context channel",
|
|
40943
|
+
defaultSeverity: "error",
|
|
40944
|
+
fixable: false,
|
|
40945
|
+
supportedEffect: [
|
|
40946
|
+
"v3",
|
|
40947
|
+
"v4"
|
|
40948
|
+
],
|
|
40949
|
+
preview: {
|
|
40950
|
+
sourceText: 'import { Effect, Layer, ServiceMap } from "effect"\n\nclass A extends ServiceMap.Service<A>()("A", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\ndeclare const layer: Layer.Layer<A, never, A>\n// @ts-expect-error\nexport const preview: Layer.Layer<A> = layer\n',
|
|
40951
|
+
diagnostics: [
|
|
40952
|
+
{
|
|
40953
|
+
start: 259,
|
|
40954
|
+
end: 266,
|
|
40955
|
+
text: "Missing 'A' in the expected Layer context. effect(missingLayerContext)"
|
|
40956
|
+
}
|
|
40957
|
+
]
|
|
40958
|
+
}
|
|
40959
|
+
},
|
|
40960
|
+
{
|
|
40961
|
+
name: "missingReturnYieldStar",
|
|
40962
|
+
group: "correctness",
|
|
40963
|
+
description: "Suggests using 'return yield*' for Effects with never success for better type narrowing",
|
|
40964
|
+
defaultSeverity: "error",
|
|
40965
|
+
fixable: true,
|
|
40966
|
+
supportedEffect: [
|
|
40967
|
+
"v3",
|
|
40968
|
+
"v4"
|
|
40969
|
+
],
|
|
40970
|
+
preview: {
|
|
40971
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n yield* Effect.log("before")\n yield* Effect.fail("boom")\n})\n',
|
|
40972
|
+
diagnostics: [
|
|
40973
|
+
{
|
|
40974
|
+
start: 121,
|
|
40975
|
+
end: 147,
|
|
40976
|
+
text: "It is recommended to use return yield* for Effects that never succeed to signal a definitive exit point for type narrowing and tooling support. effect(missingReturnYieldStar)"
|
|
40977
|
+
}
|
|
40978
|
+
]
|
|
40979
|
+
}
|
|
40980
|
+
},
|
|
40981
|
+
{
|
|
40982
|
+
name: "missingStarInYieldEffectGen",
|
|
40983
|
+
group: "correctness",
|
|
40984
|
+
description: "Enforces using 'yield*' instead of 'yield' when yielding Effects in generators",
|
|
40985
|
+
defaultSeverity: "error",
|
|
40986
|
+
fixable: true,
|
|
40987
|
+
supportedEffect: [
|
|
40988
|
+
"v3",
|
|
40989
|
+
"v4"
|
|
40990
|
+
],
|
|
40991
|
+
preview: {
|
|
40992
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n const value = yield Effect.succeed(1)\n return value\n})\n',
|
|
40993
|
+
diagnostics: [
|
|
40994
|
+
{
|
|
40995
|
+
start: 75,
|
|
40996
|
+
end: 83,
|
|
40997
|
+
text: "Seems like you used yield instead of yield* inside this Effect.gen. effect(missingStarInYieldEffectGen)"
|
|
40998
|
+
},
|
|
40999
|
+
{
|
|
41000
|
+
start: 105,
|
|
41001
|
+
end: 128,
|
|
41002
|
+
text: "When yielding Effects inside Effect.gen, you should use yield* instead of yield. effect(missingStarInYieldEffectGen)"
|
|
41003
|
+
}
|
|
41004
|
+
]
|
|
41005
|
+
}
|
|
41006
|
+
},
|
|
41007
|
+
{
|
|
41008
|
+
name: "nonObjectEffectServiceType",
|
|
41009
|
+
group: "correctness",
|
|
41010
|
+
description: "Ensures Effect.Service types are objects, not primitives",
|
|
41011
|
+
defaultSeverity: "error",
|
|
41012
|
+
fixable: false,
|
|
41013
|
+
supportedEffect: [
|
|
41014
|
+
"v3"
|
|
41015
|
+
],
|
|
41016
|
+
preview: {
|
|
41017
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nexport class BadService extends Effect.Service<BadService>()("BadService", {\n // @ts-expect-error\n succeed: "hello" as const\n}) {}\n',
|
|
41018
|
+
diagnostics: [
|
|
41019
|
+
{
|
|
41020
|
+
start: 142,
|
|
41021
|
+
end: 149,
|
|
41022
|
+
text: "Effect.Service requires the service type to be an object {} and not a primitive type. \nConsider wrapping the value in an object, or manually using Context.Tag or Effect.Tag if you want to use a primitive instead. effect(nonObjectEffectServiceType)"
|
|
41023
|
+
}
|
|
41024
|
+
]
|
|
41025
|
+
}
|
|
41026
|
+
},
|
|
41027
|
+
{
|
|
41028
|
+
name: "outdatedApi",
|
|
41029
|
+
group: "correctness",
|
|
41030
|
+
description: "Detects usage of APIs that have been removed or renamed in Effect v4",
|
|
41031
|
+
defaultSeverity: "warning",
|
|
41032
|
+
fixable: false,
|
|
41033
|
+
supportedEffect: [
|
|
41034
|
+
"v4"
|
|
41035
|
+
],
|
|
41036
|
+
preview: {
|
|
41037
|
+
sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.gen(function*() {\n // @ts-expect-error\n return yield* Effect.runtime()\n})\n',
|
|
41038
|
+
diagnostics: [
|
|
41039
|
+
{
|
|
41040
|
+
start: 0,
|
|
41041
|
+
end: 0,
|
|
41042
|
+
text: "This project targets Effect v4, but is using Effect v3 APIs. To find the correct API to use, clone and consult the github.com/effect-ts/effect-smol repository for the corresponding v4 replacement. effect(outdatedApi)"
|
|
41043
|
+
},
|
|
41044
|
+
{
|
|
41045
|
+
start: 126,
|
|
41046
|
+
end: 133,
|
|
41047
|
+
text: "runtime is an Effect v3 API, but the project is using Effect v4. effect(outdatedApi)"
|
|
41048
|
+
}
|
|
41049
|
+
]
|
|
41050
|
+
}
|
|
41051
|
+
},
|
|
41052
|
+
{
|
|
41053
|
+
name: "outdatedEffectCodegen",
|
|
41054
|
+
group: "correctness",
|
|
41055
|
+
description: "Detects when generated code is outdated and needs to be regenerated",
|
|
41056
|
+
defaultSeverity: "warning",
|
|
41057
|
+
fixable: true,
|
|
41058
|
+
supportedEffect: [
|
|
41059
|
+
"v3",
|
|
41060
|
+
"v4"
|
|
41061
|
+
],
|
|
41062
|
+
preview: {
|
|
41063
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\n// @effect-codegens accessors:stale-preview\nexport class Preview extends Effect.Service<Preview>()("Preview", {\n accessors: true,\n effect: Effect.succeed({ value: Effect.succeed(1) })\n}) {}\n',
|
|
41064
|
+
diagnostics: [
|
|
41065
|
+
{
|
|
41066
|
+
start: 61,
|
|
41067
|
+
end: 76,
|
|
41068
|
+
text: "Codegen accessors result is outdated effect(outdatedEffectCodegen)"
|
|
41069
|
+
}
|
|
41070
|
+
]
|
|
41071
|
+
}
|
|
41072
|
+
},
|
|
41073
|
+
{
|
|
41074
|
+
name: "overriddenSchemaConstructor",
|
|
41075
|
+
group: "correctness",
|
|
41076
|
+
description: "Prevents overriding constructors in Schema classes which breaks decoding behavior",
|
|
41077
|
+
defaultSeverity: "error",
|
|
41078
|
+
fixable: true,
|
|
41079
|
+
supportedEffect: [
|
|
41080
|
+
"v3",
|
|
41081
|
+
"v4"
|
|
41082
|
+
],
|
|
41083
|
+
preview: {
|
|
41084
|
+
sourceText: 'import * as Schema from "effect/Schema"\n\nexport class User extends Schema.Class<User>("User")({ name: Schema.String }) {\n constructor(readonly input: { name: string }) { super(input) }\n}\n',
|
|
41085
|
+
diagnostics: [
|
|
41086
|
+
{
|
|
41087
|
+
start: 123,
|
|
41088
|
+
end: 185,
|
|
41089
|
+
text: "Classes extending Schema must not override the constructor; this is because it silently breaks the schema decoding behaviour. If that's needed, we recommend instead to use a static 'new' method that constructs the instance. effect(overriddenSchemaConstructor)"
|
|
41090
|
+
}
|
|
41091
|
+
]
|
|
41092
|
+
}
|
|
41093
|
+
},
|
|
41094
|
+
{
|
|
41095
|
+
name: "unsupportedServiceAccessors",
|
|
41096
|
+
group: "correctness",
|
|
41097
|
+
description: "Warns about service accessors that need codegen due to generic/complex signatures",
|
|
41098
|
+
defaultSeverity: "warning",
|
|
41099
|
+
fixable: true,
|
|
41100
|
+
supportedEffect: [
|
|
41101
|
+
"v3",
|
|
41102
|
+
"v4"
|
|
41103
|
+
],
|
|
41104
|
+
preview: {
|
|
41105
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nexport class Preview extends Effect.Service<Preview>()("Preview", {\n accessors: true,\n effect: Effect.succeed({ get: <A>(value: A) => Effect.succeed(value) })\n}) {}\n',
|
|
41106
|
+
diagnostics: [
|
|
41107
|
+
{
|
|
41108
|
+
start: 54,
|
|
41109
|
+
end: 61,
|
|
41110
|
+
text: "Even if accessors are enabled, accessors for 'get' won't be available because the signature have generic type parameters or multiple call signatures. effect(unsupportedServiceAccessors)"
|
|
41111
|
+
}
|
|
41112
|
+
]
|
|
41113
|
+
}
|
|
41114
|
+
},
|
|
41115
|
+
{
|
|
41116
|
+
name: "catchUnfailableEffect",
|
|
41117
|
+
group: "antipattern",
|
|
41118
|
+
description: "Warns when using error handling on Effects that never fail (error type is 'never')",
|
|
41119
|
+
defaultSeverity: "suggestion",
|
|
41120
|
+
fixable: false,
|
|
41121
|
+
supportedEffect: [
|
|
41122
|
+
"v3",
|
|
41123
|
+
"v4"
|
|
41124
|
+
],
|
|
41125
|
+
preview: {
|
|
41126
|
+
sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.succeed(1).pipe(\n Effect.catch(() => Effect.succeed(0))\n)\n',
|
|
41127
|
+
diagnostics: [
|
|
41128
|
+
{
|
|
41129
|
+
start: 82,
|
|
41130
|
+
end: 94,
|
|
41131
|
+
text: "Looks like the previous effect never fails, so probably this error handling will never be triggered. effect(catchUnfailableEffect)"
|
|
41132
|
+
}
|
|
41133
|
+
]
|
|
41134
|
+
}
|
|
41135
|
+
},
|
|
41136
|
+
{
|
|
41137
|
+
name: "effectFnIife",
|
|
41138
|
+
group: "antipattern",
|
|
41139
|
+
description: "Effect.fn or Effect.fnUntraced is called as an IIFE (Immediately Invoked Function Expression). Use Effect.gen instead.",
|
|
41140
|
+
defaultSeverity: "warning",
|
|
41141
|
+
fixable: true,
|
|
41142
|
+
supportedEffect: [
|
|
41143
|
+
"v3",
|
|
41144
|
+
"v4"
|
|
41145
|
+
],
|
|
41146
|
+
preview: {
|
|
41147
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.fn("preview")(function*() {\n return yield* Effect.succeed(1)\n})()\n',
|
|
41148
|
+
diagnostics: [
|
|
41149
|
+
{
|
|
41150
|
+
start: 64,
|
|
41151
|
+
end: 137,
|
|
41152
|
+
text: `Effect.fn returns a reusable function that can take arguments, but here it's called immediately. Use Effect.gen instead with Effect.withSpan("preview") piped in the end to mantain tracing spans. effect(effectFnIife)`
|
|
41153
|
+
}
|
|
41154
|
+
]
|
|
41155
|
+
}
|
|
41156
|
+
},
|
|
41157
|
+
{
|
|
41158
|
+
name: "effectGenUsesAdapter",
|
|
41159
|
+
group: "antipattern",
|
|
41160
|
+
description: "Warns when using the deprecated adapter parameter in Effect.gen",
|
|
41161
|
+
defaultSeverity: "warning",
|
|
41162
|
+
fixable: false,
|
|
41163
|
+
supportedEffect: [
|
|
41164
|
+
"v3",
|
|
41165
|
+
"v4"
|
|
41166
|
+
],
|
|
41167
|
+
preview: {
|
|
41168
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*(_) {\n return yield* Effect.succeed(1)\n})\n',
|
|
41169
|
+
diagnostics: [
|
|
41170
|
+
{
|
|
41171
|
+
start: 85,
|
|
41172
|
+
end: 86,
|
|
41173
|
+
text: "The adapter of Effect.gen is not required anymore, it is now just an alias of pipe. effect(effectGenUsesAdapter)"
|
|
41174
|
+
}
|
|
41175
|
+
]
|
|
41176
|
+
}
|
|
41177
|
+
},
|
|
41178
|
+
{
|
|
41179
|
+
name: "effectInFailure",
|
|
41180
|
+
group: "antipattern",
|
|
41181
|
+
description: "Warns when an Effect is used inside an Effect failure channel",
|
|
41182
|
+
defaultSeverity: "warning",
|
|
41183
|
+
fixable: false,
|
|
41184
|
+
supportedEffect: [
|
|
41185
|
+
"v3",
|
|
41186
|
+
"v4"
|
|
41187
|
+
],
|
|
41188
|
+
preview: {
|
|
41189
|
+
sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.try({\n try: () => JSON.parse("{"),\n catch: (error) => Effect.logError(error)\n})\n',
|
|
41190
|
+
diagnostics: [
|
|
41191
|
+
{
|
|
41192
|
+
start: 46,
|
|
41193
|
+
end: 53,
|
|
41194
|
+
text: "The error channel contains an Effect (Effect<void, never, never>). Putting Effect computations in the failure channel is not intended; keep only failure types there. effect(effectInFailure)"
|
|
41195
|
+
},
|
|
41196
|
+
{
|
|
41197
|
+
start: 56,
|
|
41198
|
+
end: 144,
|
|
41199
|
+
text: "The error channel contains an Effect (Effect<void, never, never>). Putting Effect computations in the failure channel is not intended; keep only failure types there. effect(effectInFailure)"
|
|
41200
|
+
}
|
|
41201
|
+
]
|
|
41202
|
+
}
|
|
41203
|
+
},
|
|
41204
|
+
{
|
|
41205
|
+
name: "effectInVoidSuccess",
|
|
41206
|
+
group: "antipattern",
|
|
41207
|
+
description: "Detects nested Effects in void success channels that may cause unexecuted effects",
|
|
41208
|
+
defaultSeverity: "warning",
|
|
41209
|
+
fixable: false,
|
|
41210
|
+
supportedEffect: [
|
|
41211
|
+
"v3",
|
|
41212
|
+
"v4"
|
|
41213
|
+
],
|
|
41214
|
+
preview: {
|
|
41215
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview: Effect.Effect<void> = Effect.succeed(\n Effect.log("nested")\n)\n',
|
|
41216
|
+
diagnostics: [
|
|
41217
|
+
{
|
|
41218
|
+
start: 54,
|
|
41219
|
+
end: 61,
|
|
41220
|
+
text: "There is a nested 'Effect<void, never, never>' in the 'void' success channel, beware that this could lead to nested Effect<Effect<...>> that won't be executed. effect(effectInVoidSuccess)"
|
|
41221
|
+
}
|
|
41222
|
+
]
|
|
41223
|
+
}
|
|
41224
|
+
},
|
|
41225
|
+
{
|
|
41226
|
+
name: "globalErrorInEffectCatch",
|
|
41227
|
+
group: "antipattern",
|
|
41228
|
+
description: "Warns when catch callbacks return global Error type instead of typed errors",
|
|
41229
|
+
defaultSeverity: "warning",
|
|
41230
|
+
fixable: false,
|
|
41231
|
+
supportedEffect: [
|
|
41232
|
+
"v3",
|
|
41233
|
+
"v4"
|
|
41234
|
+
],
|
|
41235
|
+
preview: {
|
|
41236
|
+
sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.tryPromise({\n try: async () => 1,\n catch: (error) => new Error(String(error))\n})\n',
|
|
41237
|
+
diagnostics: [
|
|
41238
|
+
{
|
|
41239
|
+
start: 56,
|
|
41240
|
+
end: 73,
|
|
41241
|
+
text: "The 'catch' callback in Effect.tryPromise 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. effect(globalErrorInEffectCatch)"
|
|
41242
|
+
}
|
|
41243
|
+
]
|
|
41244
|
+
}
|
|
41245
|
+
},
|
|
41246
|
+
{
|
|
41247
|
+
name: "globalErrorInEffectFailure",
|
|
41248
|
+
group: "antipattern",
|
|
41249
|
+
description: "Warns when the global Error type is used in an Effect failure channel",
|
|
41250
|
+
defaultSeverity: "warning",
|
|
41251
|
+
fixable: false,
|
|
41252
|
+
supportedEffect: [
|
|
41253
|
+
"v3",
|
|
41254
|
+
"v4"
|
|
41255
|
+
],
|
|
41256
|
+
preview: {
|
|
41257
|
+
sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.fail(new Error("boom"))\n',
|
|
41258
|
+
diagnostics: [
|
|
41259
|
+
{
|
|
41260
|
+
start: 68,
|
|
41261
|
+
end: 85,
|
|
41262
|
+
text: "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. effect(globalErrorInEffectFailure)"
|
|
41263
|
+
}
|
|
41264
|
+
]
|
|
41265
|
+
}
|
|
41266
|
+
},
|
|
41267
|
+
{
|
|
41268
|
+
name: "layerMergeAllWithDependencies",
|
|
41269
|
+
group: "antipattern",
|
|
41270
|
+
description: "Detects interdependencies in Layer.mergeAll calls where one layer provides a service that another layer requires",
|
|
41271
|
+
defaultSeverity: "warning",
|
|
41272
|
+
fixable: true,
|
|
41273
|
+
supportedEffect: [
|
|
41274
|
+
"v3",
|
|
41275
|
+
"v4"
|
|
41276
|
+
],
|
|
41277
|
+
preview: {
|
|
41278
|
+
sourceText: 'import { Effect, Layer, ServiceMap } from "effect"\n\nclass A extends ServiceMap.Service<A>()("A", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\nclass B extends ServiceMap.Service<B>()("B", { make: Effect.as(A.asEffect(), {}) }) {\n static Default = Layer.effect(this, this.make)\n}\nexport const preview = Layer.mergeAll(A.Default, B.Default)\n',
|
|
41279
|
+
diagnostics: [
|
|
41280
|
+
{
|
|
41281
|
+
start: 355,
|
|
41282
|
+
end: 364,
|
|
41283
|
+
text: "This layer provides A which is required by another layer in the same Layer.mergeAll call. Layer.mergeAll creates layers in parallel, so dependencies between layers will not be satisfied. Consider moving this layer into a Layer.provideMerge after the Layer.mergeAll. effect(layerMergeAllWithDependencies)"
|
|
41284
|
+
}
|
|
41285
|
+
]
|
|
41286
|
+
}
|
|
41287
|
+
},
|
|
41288
|
+
{
|
|
41289
|
+
name: "leakingRequirements",
|
|
41290
|
+
group: "antipattern",
|
|
41291
|
+
description: "Detects implementation services leaked in service methods",
|
|
41292
|
+
defaultSeverity: "suggestion",
|
|
41293
|
+
fixable: false,
|
|
41294
|
+
supportedEffect: [
|
|
41295
|
+
"v3",
|
|
41296
|
+
"v4"
|
|
41297
|
+
],
|
|
41298
|
+
preview: {
|
|
41299
|
+
sourceText: 'import type { Effect } from "effect"\nimport { ServiceMap } from "effect"\n\nclass FileSystem extends ServiceMap.Service<FileSystem, {\n write: (s: string) => Effect.Effect<void>\n}>()("FileSystem") {}\n\nexport class Cache extends ServiceMap.Service<Cache, {\n read: Effect.Effect<string, never, FileSystem>\n save: () => Effect.Effect<void, never, FileSystem>\n}>()("Cache") {}\n',
|
|
41300
|
+
diagnostics: [
|
|
41301
|
+
{
|
|
41302
|
+
start: 212,
|
|
41303
|
+
end: 217,
|
|
41304
|
+
text: "Methods of this Service require `FileSystem` from every caller.\n\nThis leaks implementation details into the service's public type \u2014 callers shouldn't need to know *how* the service works internally, only *what* it provides.\n\nResolve these dependencies at Layer creation and provide them to each method, so the service's type reflects its purpose, not its implementation.\n\nTo suppress this diagnostic for specific dependency types that are intentionally passed through (e.g., HttpServerRequest), add `@effect-leakable-service` JSDoc to their interface declarations (e.g., the `FileSystem` interface), or to this service by adding a `@effect-expect-leaking FileSystem` JSDoc.\n\nMore info and examples at https://effect.website/docs/requirements-management/layers/#avoiding-requirement-leakage effect(leakingRequirements)"
|
|
41305
|
+
}
|
|
41306
|
+
]
|
|
41307
|
+
}
|
|
41308
|
+
},
|
|
41309
|
+
{
|
|
41310
|
+
name: "multipleEffectProvide",
|
|
41311
|
+
group: "antipattern",
|
|
41312
|
+
description: "Warns against chaining Effect.provide calls which can cause service lifecycle issues",
|
|
41313
|
+
defaultSeverity: "warning",
|
|
41314
|
+
fixable: true,
|
|
41315
|
+
supportedEffect: [
|
|
41316
|
+
"v3",
|
|
41317
|
+
"v4"
|
|
41318
|
+
],
|
|
41319
|
+
preview: {
|
|
41320
|
+
sourceText: 'import { Effect, Layer, ServiceMap } from "effect"\n\nclass A extends ServiceMap.Service<A>()("A", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\nclass B extends ServiceMap.Service<B>()("B", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\nexport const preview = Effect.void.pipe(Effect.provide(A.Default), Effect.provide(B.Default))\n',
|
|
41321
|
+
diagnostics: [
|
|
41322
|
+
{
|
|
41323
|
+
start: 348,
|
|
41324
|
+
end: 373,
|
|
41325
|
+
text: "Avoid chaining Effect.provide calls, as this can lead to service lifecycle issues. Instead, merge layers and provide them in a single call. effect(multipleEffectProvide)"
|
|
41326
|
+
}
|
|
41327
|
+
]
|
|
41328
|
+
}
|
|
41329
|
+
},
|
|
41330
|
+
{
|
|
41331
|
+
name: "returnEffectInGen",
|
|
41332
|
+
group: "antipattern",
|
|
41333
|
+
description: "Warns when returning an Effect in a generator causes nested Effect<Effect<...>>",
|
|
41334
|
+
defaultSeverity: "suggestion",
|
|
41335
|
+
fixable: true,
|
|
41336
|
+
supportedEffect: [
|
|
41337
|
+
"v3",
|
|
41338
|
+
"v4"
|
|
41339
|
+
],
|
|
41340
|
+
preview: {
|
|
41341
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n return Effect.succeed(1)\n})\n',
|
|
41342
|
+
diagnostics: [
|
|
41343
|
+
{
|
|
41344
|
+
start: 91,
|
|
41345
|
+
end: 115,
|
|
41346
|
+
text: "You are returning an Effect-able type inside a generator function, and will result in nested Effect<Effect<...>>.\nMaybe you wanted to return yield* instead?\nNested Effect-able types may be intended if you plan to later manually flatten or unwrap this Effect, if so you can safely disable this diagnostic for this line through quickfixes. effect(returnEffectInGen)"
|
|
41347
|
+
}
|
|
41348
|
+
]
|
|
41349
|
+
}
|
|
41350
|
+
},
|
|
41351
|
+
{
|
|
41352
|
+
name: "runEffectInsideEffect",
|
|
41353
|
+
group: "antipattern",
|
|
41354
|
+
description: "Suggests using Runtime methods instead of Effect.run* inside Effect contexts",
|
|
41355
|
+
defaultSeverity: "suggestion",
|
|
41356
|
+
fixable: true,
|
|
41357
|
+
supportedEffect: [
|
|
41358
|
+
"v3"
|
|
41359
|
+
],
|
|
41360
|
+
preview: {
|
|
41361
|
+
sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.gen(function*() {\n const run = () => Effect.runSync(Effect.succeed(1))\n return run()\n})\n',
|
|
41362
|
+
diagnostics: [
|
|
41363
|
+
{
|
|
41364
|
+
start: 101,
|
|
41365
|
+
end: 115,
|
|
41366
|
+
text: "Using Effect.runSync inside an Effect is not recommended. The same runtime should generally be used instead to run child effects.\nConsider extracting the Runtime by using for example Effect.runtime and then use Runtime.runSync with the extracted runtime instead. effect(runEffectInsideEffect)"
|
|
41367
|
+
}
|
|
41368
|
+
]
|
|
41369
|
+
}
|
|
41370
|
+
},
|
|
41371
|
+
{
|
|
41372
|
+
name: "schemaSyncInEffect",
|
|
41373
|
+
group: "antipattern",
|
|
41374
|
+
description: "Suggests using Effect-based Schema methods instead of sync methods inside Effect generators",
|
|
41375
|
+
defaultSeverity: "suggestion",
|
|
41376
|
+
fixable: false,
|
|
41377
|
+
supportedEffect: [
|
|
41378
|
+
"v3"
|
|
41379
|
+
],
|
|
41380
|
+
preview: {
|
|
41381
|
+
sourceText: 'import * as Effect from "effect/Effect"\nimport * as Schema from "effect/Schema"\n\nconst Person = Schema.Struct({ name: Schema.String, age: Schema.Number })\n\nexport const preview = Effect.gen(function*() {\n const input = yield* Effect.succeed({ name: "Ada", age: 1 })\n return Schema.decodeSync(Person)(input)\n})\n',
|
|
41382
|
+
diagnostics: [
|
|
41383
|
+
{
|
|
41384
|
+
start: 276,
|
|
41385
|
+
end: 293,
|
|
41386
|
+
text: "Using Schema.decodeSync inside an Effect generator is not recommended. Use Schema.decode instead to get properly typed error channel. effect(schemaSyncInEffect)"
|
|
41387
|
+
}
|
|
41388
|
+
]
|
|
41389
|
+
}
|
|
41390
|
+
},
|
|
41391
|
+
{
|
|
41392
|
+
name: "scopeInLayerEffect",
|
|
41393
|
+
group: "antipattern",
|
|
41394
|
+
description: "Suggests using Layer.scoped instead of Layer.effect when Scope is in requirements",
|
|
41395
|
+
defaultSeverity: "warning",
|
|
41396
|
+
fixable: true,
|
|
41397
|
+
supportedEffect: [
|
|
41398
|
+
"v3"
|
|
41399
|
+
],
|
|
41400
|
+
preview: {
|
|
41401
|
+
sourceText: 'import * as Context from "effect/Context"\nimport * as Effect from "effect/Effect"\nimport * as Layer from "effect/Layer"\n\nclass Cache extends Context.Tag("Cache")<Cache, { ok: true }>() {}\nexport const preview = Layer.effect(Cache, Effect.gen(function*() {\n yield* Effect.addFinalizer(() => Effect.void)\n return { ok: true as const }\n}))\n',
|
|
41402
|
+
diagnostics: [
|
|
41403
|
+
{
|
|
41404
|
+
start: 211,
|
|
41405
|
+
end: 338,
|
|
41406
|
+
text: 'Seems like you are constructing a layer with a scope in the requirements.\nConsider using "scoped" instead to get rid of the scope in the requirements. effect(scopeInLayerEffect)'
|
|
41407
|
+
}
|
|
41408
|
+
]
|
|
41409
|
+
}
|
|
41410
|
+
},
|
|
41411
|
+
{
|
|
41412
|
+
name: "strictEffectProvide",
|
|
41413
|
+
group: "antipattern",
|
|
41414
|
+
description: "Warns when using Effect.provide with layers outside of application entry points",
|
|
41415
|
+
defaultSeverity: "off",
|
|
41416
|
+
fixable: false,
|
|
41417
|
+
supportedEffect: [
|
|
41418
|
+
"v3",
|
|
41419
|
+
"v4"
|
|
41420
|
+
],
|
|
41421
|
+
preview: {
|
|
41422
|
+
sourceText: 'import { Effect, Layer, ServiceMap } from "effect"\n\nclass Config extends ServiceMap.Service<Config>()("Config", { make: Effect.succeed({}) }) {\n static Default = Layer.effect(this, this.make)\n}\nexport const preview = Effect.void.pipe(Effect.provide(Config.Default))\n',
|
|
41423
|
+
diagnostics: [
|
|
41424
|
+
{
|
|
41425
|
+
start: 235,
|
|
41426
|
+
end: 265,
|
|
41427
|
+
text: "Effect.provide with a Layer should only be used at application entry points. If this is an entry point, you can safely disable this diagnostic. Otherwise, using Effect.provide may break scope lifetimes. Compose all layers at your entry point and provide them at once. effect(strictEffectProvide)"
|
|
41428
|
+
}
|
|
41429
|
+
]
|
|
41430
|
+
}
|
|
41431
|
+
},
|
|
41432
|
+
{
|
|
41433
|
+
name: "tryCatchInEffectGen",
|
|
41434
|
+
group: "antipattern",
|
|
41435
|
+
description: "Discourages try/catch in Effect generators in favor of Effect error handling",
|
|
41436
|
+
defaultSeverity: "suggestion",
|
|
41437
|
+
fixable: false,
|
|
41438
|
+
supportedEffect: [
|
|
41439
|
+
"v3",
|
|
41440
|
+
"v4"
|
|
41441
|
+
],
|
|
41442
|
+
preview: {
|
|
41443
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n try { return yield* Effect.succeed(1) }\n catch { return 0 }\n})\n',
|
|
41444
|
+
diagnostics: [
|
|
41445
|
+
{
|
|
41446
|
+
start: 91,
|
|
41447
|
+
end: 151,
|
|
41448
|
+
text: "Avoid using try/catch inside Effect generators. Use Effect's error handling mechanisms instead (e.g. Effect.try, Effect.tryPromise, Effect.catch, Effect.catchTag). effect(tryCatchInEffectGen)"
|
|
41449
|
+
}
|
|
41450
|
+
]
|
|
41451
|
+
}
|
|
41452
|
+
},
|
|
41453
|
+
{
|
|
41454
|
+
name: "unknownInEffectCatch",
|
|
41455
|
+
group: "antipattern",
|
|
41456
|
+
description: "Warns when catch callbacks return unknown instead of typed errors",
|
|
41457
|
+
defaultSeverity: "warning",
|
|
41458
|
+
fixable: false,
|
|
41459
|
+
supportedEffect: [
|
|
41460
|
+
"v3",
|
|
41461
|
+
"v4"
|
|
41462
|
+
],
|
|
41463
|
+
preview: {
|
|
41464
|
+
sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.tryPromise({\n try: async () => 1,\n catch: (error) => error\n})\n',
|
|
41465
|
+
diagnostics: [
|
|
41466
|
+
{
|
|
41467
|
+
start: 56,
|
|
41468
|
+
end: 73,
|
|
41469
|
+
text: "The 'catch' callback in Effect.tryPromise returns 'unknown'. The catch callback should be used to provide typed errors.\nConsider wrapping unknown errors into Effect's Data.TaggedError for example, or narrow down the type to the specific error raised. effect(unknownInEffectCatch)"
|
|
41470
|
+
}
|
|
41471
|
+
]
|
|
41472
|
+
}
|
|
41473
|
+
},
|
|
41474
|
+
{
|
|
41475
|
+
name: "extendsNativeError",
|
|
41476
|
+
group: "effectNative",
|
|
41477
|
+
description: "Warns when a class directly extends the native Error class",
|
|
41478
|
+
defaultSeverity: "off",
|
|
41479
|
+
fixable: false,
|
|
41480
|
+
supportedEffect: [
|
|
41481
|
+
"v3",
|
|
41482
|
+
"v4"
|
|
41483
|
+
],
|
|
41484
|
+
preview: {
|
|
41485
|
+
sourceText: "\nexport class PreviewError extends Error {}\n",
|
|
41486
|
+
diagnostics: [
|
|
41487
|
+
{
|
|
41488
|
+
start: 14,
|
|
41489
|
+
end: 26,
|
|
41490
|
+
text: "Avoid extending the native 'Error' class directly. Consider using a tagged error (e.g. Data.TaggedError) to maintain type safety in the Effect failure channel. effect(extendsNativeError)"
|
|
41491
|
+
}
|
|
41492
|
+
]
|
|
41493
|
+
}
|
|
41494
|
+
},
|
|
41495
|
+
{
|
|
41496
|
+
name: "globalFetch",
|
|
41497
|
+
group: "effectNative",
|
|
41498
|
+
description: "Warns when using the global fetch function instead of the Effect HTTP client",
|
|
41499
|
+
defaultSeverity: "off",
|
|
41500
|
+
fixable: false,
|
|
41501
|
+
supportedEffect: [
|
|
41502
|
+
"v3",
|
|
41503
|
+
"v4"
|
|
41504
|
+
],
|
|
41505
|
+
preview: {
|
|
41506
|
+
sourceText: '\nexport const preview = fetch("https://example.com")\n',
|
|
41507
|
+
diagnostics: [
|
|
41508
|
+
{
|
|
41509
|
+
start: 24,
|
|
41510
|
+
end: 29,
|
|
41511
|
+
text: "Prefer using HttpClient from @effect/platform instead of the global 'fetch' function. effect(globalFetch)"
|
|
41512
|
+
}
|
|
41513
|
+
]
|
|
41514
|
+
}
|
|
41515
|
+
},
|
|
41516
|
+
{
|
|
41517
|
+
name: "instanceOfSchema",
|
|
41518
|
+
group: "effectNative",
|
|
41519
|
+
description: "Suggests using Schema.is instead of instanceof for Effect Schema types",
|
|
41520
|
+
defaultSeverity: "off",
|
|
41521
|
+
fixable: true,
|
|
41522
|
+
supportedEffect: [
|
|
41523
|
+
"v3",
|
|
41524
|
+
"v4"
|
|
41525
|
+
],
|
|
41526
|
+
preview: {
|
|
41527
|
+
sourceText: 'import { Schema } from "effect"\n\nclass User extends Schema.Class<User>("User")({ name: Schema.String }) {}\ndeclare const value: unknown\nexport const preview = value instanceof User\n',
|
|
41528
|
+
diagnostics: [
|
|
41529
|
+
{
|
|
41530
|
+
start: 159,
|
|
41531
|
+
end: 180,
|
|
41532
|
+
text: "Consider using Schema.is instead of instanceof for Effect Schema types. effect(instanceOfSchema)"
|
|
41533
|
+
}
|
|
41534
|
+
]
|
|
41535
|
+
}
|
|
41536
|
+
},
|
|
41537
|
+
{
|
|
41538
|
+
name: "nodeBuiltinImport",
|
|
41539
|
+
group: "effectNative",
|
|
41540
|
+
description: "Warns when importing Node.js built-in modules that have Effect-native counterparts",
|
|
41541
|
+
defaultSeverity: "off",
|
|
41542
|
+
fixable: false,
|
|
41543
|
+
supportedEffect: [
|
|
41544
|
+
"v3",
|
|
41545
|
+
"v4"
|
|
41546
|
+
],
|
|
41547
|
+
preview: {
|
|
41548
|
+
sourceText: 'import fs from "node:fs"\n\nexport const preview = fs.readFileSync\n',
|
|
41549
|
+
diagnostics: [
|
|
41550
|
+
{
|
|
41551
|
+
start: 15,
|
|
41552
|
+
end: 24,
|
|
41553
|
+
text: "Prefer using FileSystem from @effect/platform instead of the Node.js 'fs' module. effect(nodeBuiltinImport)"
|
|
41554
|
+
}
|
|
41555
|
+
]
|
|
41556
|
+
}
|
|
41557
|
+
},
|
|
41558
|
+
{
|
|
41559
|
+
name: "preferSchemaOverJson",
|
|
41560
|
+
group: "effectNative",
|
|
41561
|
+
description: "Suggests using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify which may throw",
|
|
41562
|
+
defaultSeverity: "suggestion",
|
|
41563
|
+
fixable: false,
|
|
41564
|
+
supportedEffect: [
|
|
41565
|
+
"v3",
|
|
41566
|
+
"v4"
|
|
41567
|
+
],
|
|
41568
|
+
preview: {
|
|
41569
|
+
sourceText: `import { Effect } from "effect"
|
|
41570
|
+
|
|
41571
|
+
export const preview = Effect.gen(function*() {
|
|
41572
|
+
const text = yield* Effect.succeed('{"ok":true}')
|
|
41573
|
+
return JSON.parse(text)
|
|
41574
|
+
})
|
|
41575
|
+
`,
|
|
41576
|
+
diagnostics: [
|
|
41577
|
+
{
|
|
41578
|
+
start: 142,
|
|
41579
|
+
end: 158,
|
|
41580
|
+
text: "Consider using Effect Schema for JSON operations instead of JSON.parse/JSON.stringify effect(preferSchemaOverJson)"
|
|
41581
|
+
}
|
|
41582
|
+
]
|
|
41583
|
+
}
|
|
41584
|
+
},
|
|
41585
|
+
{
|
|
41586
|
+
name: "catchAllToMapError",
|
|
41587
|
+
group: "style",
|
|
41588
|
+
description: "Suggests using Effect.mapError instead of Effect.catchAll when the callback only wraps the error with Effect.fail",
|
|
41589
|
+
defaultSeverity: "suggestion",
|
|
41590
|
+
fixable: true,
|
|
41591
|
+
supportedEffect: [
|
|
41592
|
+
"v3",
|
|
41593
|
+
"v4"
|
|
41594
|
+
],
|
|
41595
|
+
preview: {
|
|
41596
|
+
sourceText: 'import { Effect } from "effect"\n\nclass WrappedError {\n constructor(readonly cause: unknown) {}\n}\n\nexport const preview = Effect.fail("boom").pipe(\n Effect.catch((cause) => Effect.fail(new WrappedError(cause)))\n)\n',
|
|
41597
|
+
diagnostics: [
|
|
41598
|
+
{
|
|
41599
|
+
start: 150,
|
|
41600
|
+
end: 162,
|
|
41601
|
+
text: "You can use Effect.mapError instead of Effect.catch + Effect.fail to transform the error type. effect(catchAllToMapError)"
|
|
41602
|
+
}
|
|
41603
|
+
]
|
|
41604
|
+
}
|
|
41605
|
+
},
|
|
41606
|
+
{
|
|
41607
|
+
name: "deterministicKeys",
|
|
41608
|
+
group: "style",
|
|
41609
|
+
description: "Enforces deterministic naming for service/tag/error identifiers based on class names",
|
|
41610
|
+
defaultSeverity: "off",
|
|
41611
|
+
fixable: true,
|
|
41612
|
+
supportedEffect: [
|
|
41613
|
+
"v3",
|
|
41614
|
+
"v4"
|
|
41615
|
+
],
|
|
41616
|
+
preview: {
|
|
41617
|
+
sourceText: 'import { ServiceMap } from "effect"\n\nexport class RenamedService\n extends ServiceMap.Service<RenamedService, {}>()("CustomIdentifier") {}\n',
|
|
41618
|
+
diagnostics: [
|
|
41619
|
+
{
|
|
41620
|
+
start: 116,
|
|
41621
|
+
end: 134,
|
|
41622
|
+
text: "Key should be '@effect/harness-effect-v4/examples/diagnostics/deterministicKeys_preview/RenamedService' effect(deterministicKeys)"
|
|
41623
|
+
}
|
|
41624
|
+
]
|
|
41625
|
+
}
|
|
41626
|
+
},
|
|
41627
|
+
{
|
|
41628
|
+
name: "effectFnOpportunity",
|
|
41629
|
+
group: "style",
|
|
41630
|
+
description: "Suggests using Effect.fn for functions that returns an Effect",
|
|
41631
|
+
defaultSeverity: "suggestion",
|
|
41632
|
+
fixable: true,
|
|
41633
|
+
supportedEffect: [
|
|
41634
|
+
"v3",
|
|
41635
|
+
"v4"
|
|
41636
|
+
],
|
|
41637
|
+
preview: {
|
|
41638
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = () => Effect.gen(function*() {\n return yield* Effect.succeed(1)\n})\n',
|
|
41639
|
+
diagnostics: [
|
|
41640
|
+
{
|
|
41641
|
+
start: 54,
|
|
41642
|
+
end: 61,
|
|
41643
|
+
text: "Can be rewritten as a reusable function: Effect.fn(function*() { ... }) effect(effectFnOpportunity)"
|
|
41644
|
+
}
|
|
41645
|
+
]
|
|
41646
|
+
}
|
|
41647
|
+
},
|
|
41648
|
+
{
|
|
41649
|
+
name: "effectMapVoid",
|
|
41650
|
+
group: "style",
|
|
41651
|
+
description: "Suggests using Effect.asVoid instead of Effect.map(() => void 0), Effect.map(() => undefined), or Effect.map(() => {})",
|
|
41652
|
+
defaultSeverity: "suggestion",
|
|
41653
|
+
fixable: true,
|
|
41654
|
+
supportedEffect: [
|
|
41655
|
+
"v3",
|
|
41656
|
+
"v4"
|
|
41657
|
+
],
|
|
41658
|
+
preview: {
|
|
41659
|
+
sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.succeed(1).pipe(\n Effect.map(() => undefined)\n)\n',
|
|
41660
|
+
diagnostics: [
|
|
41661
|
+
{
|
|
41662
|
+
start: 82,
|
|
41663
|
+
end: 92,
|
|
41664
|
+
text: "Effect.asVoid can be used instead to discard the success value effect(effectMapVoid)"
|
|
41665
|
+
}
|
|
41666
|
+
]
|
|
41667
|
+
}
|
|
41668
|
+
},
|
|
41669
|
+
{
|
|
41670
|
+
name: "effectSucceedWithVoid",
|
|
41671
|
+
group: "style",
|
|
41672
|
+
description: "Suggests using Effect.void instead of Effect.succeed(undefined) or Effect.succeed(void 0)",
|
|
41673
|
+
defaultSeverity: "suggestion",
|
|
41674
|
+
fixable: true,
|
|
41675
|
+
supportedEffect: [
|
|
41676
|
+
"v3",
|
|
41677
|
+
"v4"
|
|
41678
|
+
],
|
|
41679
|
+
preview: {
|
|
41680
|
+
sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.succeed(undefined)\n',
|
|
41681
|
+
diagnostics: [
|
|
41682
|
+
{
|
|
41683
|
+
start: 56,
|
|
41684
|
+
end: 81,
|
|
41685
|
+
text: "Effect.void can be used instead of Effect.succeed(undefined) or Effect.succeed(void 0) effect(effectSucceedWithVoid)"
|
|
41686
|
+
}
|
|
41687
|
+
]
|
|
41688
|
+
}
|
|
41689
|
+
},
|
|
41690
|
+
{
|
|
41691
|
+
name: "importFromBarrel",
|
|
41692
|
+
group: "style",
|
|
41693
|
+
description: "Suggests importing from specific module paths instead of barrel exports",
|
|
41694
|
+
defaultSeverity: "off",
|
|
41695
|
+
fixable: true,
|
|
41696
|
+
supportedEffect: [
|
|
41697
|
+
"v3",
|
|
41698
|
+
"v4"
|
|
41699
|
+
],
|
|
41700
|
+
preview: {
|
|
41701
|
+
sourceText: 'import { Effect } from "effect"\n\nexport const preview = Effect.void\n',
|
|
41702
|
+
diagnostics: [
|
|
41703
|
+
{
|
|
41704
|
+
start: 9,
|
|
41705
|
+
end: 15,
|
|
41706
|
+
text: "Importing from barrel module effect is not allowed. effect(importFromBarrel)"
|
|
41707
|
+
}
|
|
41708
|
+
]
|
|
41709
|
+
}
|
|
41710
|
+
},
|
|
41711
|
+
{
|
|
41712
|
+
name: "missedPipeableOpportunity",
|
|
41713
|
+
group: "style",
|
|
41714
|
+
description: "Enforces the use of pipeable style for nested function calls",
|
|
41715
|
+
defaultSeverity: "off",
|
|
41716
|
+
fixable: true,
|
|
41717
|
+
supportedEffect: [
|
|
41718
|
+
"v3",
|
|
41719
|
+
"v4"
|
|
41720
|
+
],
|
|
41721
|
+
preview: {
|
|
41722
|
+
sourceText: 'import { identity, Schema } from "effect"\n\nconst User = Schema.Struct({ id: Schema.Number })\nexport const preview = identity(identity(Schema.decodeEffect(User)({ id: 1 })))\n',
|
|
41723
|
+
diagnostics: [
|
|
41724
|
+
{
|
|
41725
|
+
start: 116,
|
|
41726
|
+
end: 172,
|
|
41727
|
+
text: "Nested function calls can be converted to pipeable style for better readability; consider using Schema.decodeEffect(User)({ id: 1 }).pipe(...) instead. effect(missedPipeableOpportunity)"
|
|
41728
|
+
}
|
|
41729
|
+
]
|
|
41730
|
+
}
|
|
41731
|
+
},
|
|
41732
|
+
{
|
|
41733
|
+
name: "missingEffectServiceDependency",
|
|
41734
|
+
group: "style",
|
|
41735
|
+
description: "Checks that Effect.Service dependencies satisfy all required layer inputs",
|
|
41736
|
+
defaultSeverity: "off",
|
|
41737
|
+
fixable: false,
|
|
41738
|
+
supportedEffect: [
|
|
41739
|
+
"v3"
|
|
41740
|
+
],
|
|
41741
|
+
preview: {
|
|
41742
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nclass Db extends Effect.Service<Db>()("Db", { succeed: { ok: true } }) {}\nexport class Repo extends Effect.Service<Repo>()("Repo", {\n effect: Effect.gen(function*() {\n yield* Db\n return { all: Effect.succeed([] as Array<number>) }\n })\n}) {}\n',
|
|
41743
|
+
diagnostics: [
|
|
41744
|
+
{
|
|
41745
|
+
start: 128,
|
|
41746
|
+
end: 132,
|
|
41747
|
+
text: "Service 'Db' is required but not provided by dependencies effect(missingEffectServiceDependency)"
|
|
41748
|
+
}
|
|
41749
|
+
]
|
|
41750
|
+
}
|
|
41751
|
+
},
|
|
41752
|
+
{
|
|
41753
|
+
name: "redundantSchemaTagIdentifier",
|
|
41754
|
+
group: "style",
|
|
41755
|
+
description: "Suggests removing redundant identifier argument when it equals the tag value in Schema.TaggedClass/TaggedError/TaggedRequest",
|
|
41756
|
+
defaultSeverity: "suggestion",
|
|
41757
|
+
fixable: true,
|
|
41758
|
+
supportedEffect: [
|
|
41759
|
+
"v3",
|
|
41760
|
+
"v4"
|
|
41761
|
+
],
|
|
41762
|
+
preview: {
|
|
41763
|
+
sourceText: 'import * as Schema from "effect/Schema"\n\nexport class Preview\n extends Schema.TaggedClass<Preview>("Preview")("Preview", {\n value: Schema.String\n }) {}\n',
|
|
41764
|
+
diagnostics: [
|
|
41765
|
+
{
|
|
41766
|
+
start: 100,
|
|
41767
|
+
end: 109,
|
|
41768
|
+
text: "Identifier 'Preview' is redundant since it equals the _tag value effect(redundantSchemaTagIdentifier)"
|
|
41769
|
+
}
|
|
41770
|
+
]
|
|
41771
|
+
}
|
|
41772
|
+
},
|
|
41773
|
+
{
|
|
41774
|
+
name: "schemaStructWithTag",
|
|
41775
|
+
group: "style",
|
|
41776
|
+
description: "Suggests using Schema.TaggedStruct instead of Schema.Struct with _tag field",
|
|
41777
|
+
defaultSeverity: "suggestion",
|
|
41778
|
+
fixable: true,
|
|
41779
|
+
supportedEffect: [
|
|
41780
|
+
"v3",
|
|
41781
|
+
"v4"
|
|
41782
|
+
],
|
|
41783
|
+
preview: {
|
|
41784
|
+
sourceText: 'import * as Schema from "effect/Schema"\n\nexport const preview = Schema.Struct({\n _tag: Schema.Literal("User"),\n name: Schema.String\n})\n',
|
|
41785
|
+
diagnostics: [
|
|
41786
|
+
{
|
|
41787
|
+
start: 64,
|
|
41788
|
+
end: 136,
|
|
41789
|
+
text: "Schema.Struct with a _tag field can be simplified to Schema.TaggedStruct to make the tag optional in the constructor. effect(schemaStructWithTag)"
|
|
41790
|
+
}
|
|
41791
|
+
]
|
|
41792
|
+
}
|
|
41793
|
+
},
|
|
41794
|
+
{
|
|
41795
|
+
name: "schemaUnionOfLiterals",
|
|
41796
|
+
group: "style",
|
|
41797
|
+
description: "Simplifies Schema.Union of multiple Schema.Literal calls into single Schema.Literal",
|
|
41798
|
+
defaultSeverity: "off",
|
|
41799
|
+
fixable: true,
|
|
41800
|
+
supportedEffect: [
|
|
41801
|
+
"v3"
|
|
41802
|
+
],
|
|
41803
|
+
preview: {
|
|
41804
|
+
sourceText: 'import * as Schema from "effect/Schema"\n\nexport const preview = Schema.Union(Schema.Literal("a"), Schema.Literal("b"))\n',
|
|
41805
|
+
diagnostics: [
|
|
41806
|
+
{
|
|
41807
|
+
start: 64,
|
|
41808
|
+
end: 118,
|
|
41809
|
+
text: "A Schema.Union of multiple Schema.Literal calls can be simplified to a single Schema.Literal call. effect(schemaUnionOfLiterals)"
|
|
41810
|
+
}
|
|
41811
|
+
]
|
|
41812
|
+
}
|
|
41813
|
+
},
|
|
41814
|
+
{
|
|
41815
|
+
name: "serviceNotAsClass",
|
|
41816
|
+
group: "style",
|
|
41817
|
+
description: "Warns when ServiceMap.Service is used as a variable instead of a class declaration",
|
|
41818
|
+
defaultSeverity: "off",
|
|
41819
|
+
fixable: true,
|
|
41820
|
+
supportedEffect: [
|
|
41821
|
+
"v4"
|
|
41822
|
+
],
|
|
41823
|
+
preview: {
|
|
41824
|
+
sourceText: 'import { ServiceMap } from "effect"\n\nexport const Preview = ServiceMap.Service<{ port: number }>("Preview")\n',
|
|
41825
|
+
diagnostics: [
|
|
41826
|
+
{
|
|
41827
|
+
start: 60,
|
|
41828
|
+
end: 107,
|
|
41829
|
+
text: 'ServiceMap.Service should be used in a class declaration instead of as a variable. Use: class Preview extends ServiceMap.Service<Preview, { port: number }>()("Preview") {} effect(serviceNotAsClass)'
|
|
41830
|
+
}
|
|
41831
|
+
]
|
|
41832
|
+
}
|
|
41833
|
+
},
|
|
41834
|
+
{
|
|
41835
|
+
name: "strictBooleanExpressions",
|
|
41836
|
+
group: "style",
|
|
41837
|
+
description: "Enforces boolean types in conditional expressions for type safety",
|
|
41838
|
+
defaultSeverity: "off",
|
|
41839
|
+
fixable: false,
|
|
41840
|
+
supportedEffect: [
|
|
41841
|
+
"v3",
|
|
41842
|
+
"v4"
|
|
41843
|
+
],
|
|
41844
|
+
preview: {
|
|
41845
|
+
sourceText: "\ndeclare const value: string | undefined\nexport const preview = value ? 1 : 0\n",
|
|
41846
|
+
diagnostics: [
|
|
41847
|
+
{
|
|
41848
|
+
start: 64,
|
|
41849
|
+
end: 69,
|
|
41850
|
+
text: "Unexpected `string` type in condition, expected strictly a boolean instead. effect(strictBooleanExpressions)"
|
|
41851
|
+
},
|
|
41852
|
+
{
|
|
41853
|
+
start: 64,
|
|
41854
|
+
end: 69,
|
|
41855
|
+
text: "Unexpected `undefined` type in condition, expected strictly a boolean instead. effect(strictBooleanExpressions)"
|
|
41856
|
+
}
|
|
41857
|
+
]
|
|
41858
|
+
}
|
|
41859
|
+
},
|
|
41860
|
+
{
|
|
41861
|
+
name: "unnecessaryEffectGen",
|
|
41862
|
+
group: "style",
|
|
41863
|
+
description: "Suggests removing Effect.gen when it contains only a single return statement",
|
|
41864
|
+
defaultSeverity: "suggestion",
|
|
41865
|
+
fixable: true,
|
|
41866
|
+
supportedEffect: [
|
|
41867
|
+
"v3",
|
|
41868
|
+
"v4"
|
|
41869
|
+
],
|
|
41870
|
+
preview: {
|
|
41871
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.gen(function*() {\n return yield* Effect.succeed(1)\n})\n',
|
|
41872
|
+
diagnostics: [
|
|
41873
|
+
{
|
|
41874
|
+
start: 64,
|
|
41875
|
+
end: 125,
|
|
41876
|
+
text: "This Effect.gen contains a single return statement. effect(unnecessaryEffectGen)"
|
|
41877
|
+
}
|
|
41878
|
+
]
|
|
41879
|
+
}
|
|
41880
|
+
},
|
|
41881
|
+
{
|
|
41882
|
+
name: "unnecessaryFailYieldableError",
|
|
41883
|
+
group: "style",
|
|
41884
|
+
description: "Suggests yielding yieldable errors directly instead of wrapping with Effect.fail",
|
|
41885
|
+
defaultSeverity: "suggestion",
|
|
41886
|
+
fixable: true,
|
|
41887
|
+
supportedEffect: [
|
|
41888
|
+
"v3",
|
|
41889
|
+
"v4"
|
|
41890
|
+
],
|
|
41891
|
+
preview: {
|
|
41892
|
+
sourceText: 'import * as Data from "effect/Data"\nimport * as Effect from "effect/Effect"\n\nclass Boom extends Data.TaggedError("Boom")<{}> {}\nexport const preview = Effect.gen(function*() {\n yield* Effect.fail(new Boom())\n})\n',
|
|
41893
|
+
diagnostics: [
|
|
41894
|
+
{
|
|
41895
|
+
start: 178,
|
|
41896
|
+
end: 208,
|
|
41897
|
+
text: "This Effect.fail call uses a yieldable error type as argument. You can yield* the error directly instead. effect(unnecessaryFailYieldableError)"
|
|
41898
|
+
}
|
|
41899
|
+
]
|
|
41900
|
+
}
|
|
41901
|
+
},
|
|
41902
|
+
{
|
|
41903
|
+
name: "unnecessaryPipe",
|
|
41904
|
+
group: "style",
|
|
41905
|
+
description: "Removes pipe calls with no arguments",
|
|
41906
|
+
defaultSeverity: "suggestion",
|
|
41907
|
+
fixable: true,
|
|
41908
|
+
supportedEffect: [
|
|
41909
|
+
"v3",
|
|
41910
|
+
"v4"
|
|
41911
|
+
],
|
|
41912
|
+
preview: {
|
|
41913
|
+
sourceText: 'import { pipe } from "effect/Function"\n\nexport const preview = pipe(1)\n',
|
|
41914
|
+
diagnostics: [
|
|
41915
|
+
{
|
|
41916
|
+
start: 63,
|
|
41917
|
+
end: 70,
|
|
41918
|
+
text: "This pipe call contains no arguments. effect(unnecessaryPipe)"
|
|
41919
|
+
}
|
|
41920
|
+
]
|
|
41921
|
+
}
|
|
41922
|
+
},
|
|
41923
|
+
{
|
|
41924
|
+
name: "unnecessaryPipeChain",
|
|
41925
|
+
group: "style",
|
|
41926
|
+
description: "Simplifies chained pipe calls into a single pipe call",
|
|
41927
|
+
defaultSeverity: "suggestion",
|
|
41928
|
+
fixable: true,
|
|
41929
|
+
supportedEffect: [
|
|
41930
|
+
"v3",
|
|
41931
|
+
"v4"
|
|
41932
|
+
],
|
|
41933
|
+
preview: {
|
|
41934
|
+
sourceText: 'import * as Effect from "effect/Effect"\n\nexport const preview = Effect.succeed(1).pipe(Effect.asVoid).pipe(Effect.as("done"))\n',
|
|
41935
|
+
diagnostics: [
|
|
41936
|
+
{
|
|
41937
|
+
start: 64,
|
|
41938
|
+
end: 125,
|
|
41939
|
+
text: "Chained pipe calls can be simplified to a single pipe call effect(unnecessaryPipeChain)"
|
|
41940
|
+
}
|
|
41941
|
+
]
|
|
41942
|
+
}
|
|
41943
|
+
}
|
|
41944
|
+
]
|
|
41945
|
+
};
|
|
41946
|
+
|
|
41947
|
+
// src/cli/setup/diagnostic-info.ts
|
|
41948
|
+
var diagnosticMetadata = metadata_default;
|
|
41949
|
+
function getDiagnosticGroups() {
|
|
41950
|
+
return diagnosticMetadata.groups;
|
|
40674
41951
|
}
|
|
40675
|
-
function
|
|
41952
|
+
function getDiagnosticMetadataRules() {
|
|
41953
|
+
return diagnosticMetadata.rules;
|
|
41954
|
+
}
|
|
41955
|
+
function getAllDiagnostics() {
|
|
41956
|
+
const diagnosticsByName = new Map(diagnostics.map((diagnostic) => [diagnostic.name, diagnostic]));
|
|
41957
|
+
return getDiagnosticMetadataRules().flatMap((metadataRule) => {
|
|
41958
|
+
const diagnostic = diagnosticsByName.get(metadataRule.name);
|
|
41959
|
+
if (!diagnostic) {
|
|
41960
|
+
return [];
|
|
41961
|
+
}
|
|
41962
|
+
return [{
|
|
41963
|
+
name: diagnostic.name,
|
|
41964
|
+
code: diagnostic.code,
|
|
41965
|
+
group: metadataRule.group,
|
|
41966
|
+
defaultSeverity: diagnostic.severity,
|
|
41967
|
+
description: metadataRule.description,
|
|
41968
|
+
preview: metadataRule.preview
|
|
41969
|
+
}];
|
|
41970
|
+
});
|
|
41971
|
+
}
|
|
41972
|
+
function cycleSeverity(current, direction) {
|
|
41973
|
+
const order = ["off", "suggestion", "message", "warning", "error"];
|
|
41974
|
+
const currentIndex = order.indexOf(current);
|
|
41975
|
+
if (direction === "right") {
|
|
41976
|
+
return order[(currentIndex + 1) % order.length];
|
|
41977
|
+
} else {
|
|
41978
|
+
return order[(currentIndex - 1 + order.length) % order.length];
|
|
41979
|
+
}
|
|
41980
|
+
}
|
|
41981
|
+
var shortNames = {
|
|
41982
|
+
off: "off",
|
|
41983
|
+
suggestion: "sugg",
|
|
41984
|
+
message: "info",
|
|
41985
|
+
warning: "warn",
|
|
41986
|
+
error: "err"
|
|
41987
|
+
};
|
|
41988
|
+
var MAX_SEVERITY_LENGTH = Object.values(shortNames).reduce((max2, name) => Math.max(max2, name.length), 0);
|
|
41989
|
+
function getSeverityShortName(severity) {
|
|
41990
|
+
return shortNames[severity] ?? "???";
|
|
41991
|
+
}
|
|
41992
|
+
|
|
41993
|
+
// src/cli/setup/diagnostic-prompt.ts
|
|
41994
|
+
var diagnosticGroups = getDiagnosticGroups();
|
|
41995
|
+
var Action2 = taggedEnum();
|
|
41996
|
+
var SEARCH_ICON = "/";
|
|
41997
|
+
var MIN_PREVIEW_AND_MESSAGES_LINES = 18;
|
|
41998
|
+
function getControlsLegend(searchText) {
|
|
41999
|
+
const searchLegend = searchText.length === 0 ? `${SEARCH_ICON} type to search` : `${SEARCH_ICON} searching: ${searchText}`;
|
|
42000
|
+
return `\u2190/\u2192 change rule \u2191/\u2193 change severity ${searchLegend}`;
|
|
42001
|
+
}
|
|
42002
|
+
function getSeveritySymbol(severity) {
|
|
42003
|
+
const symbols = {
|
|
42004
|
+
off: ".",
|
|
42005
|
+
suggestion: "?",
|
|
42006
|
+
message: "i",
|
|
42007
|
+
warning: "!",
|
|
42008
|
+
error: "x"
|
|
42009
|
+
};
|
|
42010
|
+
return symbols[severity];
|
|
42011
|
+
}
|
|
42012
|
+
function getSeverityStyle(severity) {
|
|
42013
|
+
const styles = {
|
|
42014
|
+
off: DIM,
|
|
42015
|
+
suggestion: DIM,
|
|
42016
|
+
message: BLUE,
|
|
42017
|
+
warning: YELLOW,
|
|
42018
|
+
error: RED
|
|
42019
|
+
};
|
|
42020
|
+
return styles[severity];
|
|
42021
|
+
}
|
|
42022
|
+
function renderEntry(entry, severity, isSelected) {
|
|
42023
|
+
const symbol4 = ansi(getSeveritySymbol(severity), getSeverityStyle(severity));
|
|
42024
|
+
const name = isSelected ? ansi(entry.name, UNDERLINE) : entry.name;
|
|
42025
|
+
return `${symbol4} ${name}`;
|
|
42026
|
+
}
|
|
42027
|
+
function rowsForLength(length, columns) {
|
|
42028
|
+
if (columns <= 0) {
|
|
42029
|
+
return 1;
|
|
42030
|
+
}
|
|
42031
|
+
return Math.max(1, 1 + Math.floor(Math.max(length - 1, 0) / columns));
|
|
42032
|
+
}
|
|
42033
|
+
function eraseRenderedLines(lines2, columns) {
|
|
42034
|
+
let result3 = "";
|
|
42035
|
+
for (let lineIndex = lines2.length - 1; lineIndex >= 0; lineIndex--) {
|
|
42036
|
+
const rows = rowsForLength(visibleLength(lines2[lineIndex] ?? ""), columns);
|
|
42037
|
+
for (let rowIndex = 0; rowIndex < rows; rowIndex++) {
|
|
42038
|
+
result3 += ERASE_LINE;
|
|
42039
|
+
if (!(lineIndex === 0 && rowIndex === rows - 1)) {
|
|
42040
|
+
result3 += "\x1B[1A";
|
|
42041
|
+
}
|
|
42042
|
+
}
|
|
42043
|
+
}
|
|
42044
|
+
if (lines2.length > 0) {
|
|
42045
|
+
result3 += CURSOR_LEFT;
|
|
42046
|
+
}
|
|
42047
|
+
return result3;
|
|
42048
|
+
}
|
|
42049
|
+
function wrapPaddedText(text2, initialPadding, endPadding, columns) {
|
|
42050
|
+
if (text2.length === 0) {
|
|
42051
|
+
return [initialPadding + endPadding];
|
|
42052
|
+
}
|
|
42053
|
+
const available = Math.max(columns - visibleLength(initialPadding) - visibleLength(endPadding), 1);
|
|
42054
|
+
const lines2 = [];
|
|
42055
|
+
let remaining = text2;
|
|
42056
|
+
while (remaining.length > 0) {
|
|
42057
|
+
const chunk = remaining.slice(0, available);
|
|
42058
|
+
lines2.push(initialPadding + chunk + endPadding);
|
|
42059
|
+
remaining = remaining.slice(chunk.length);
|
|
42060
|
+
}
|
|
42061
|
+
return lines2;
|
|
42062
|
+
}
|
|
42063
|
+
function wrapListItemText(text2, firstLinePadding, continuationPadding, endPadding, columns) {
|
|
42064
|
+
if (text2.length === 0) {
|
|
42065
|
+
return [firstLinePadding + endPadding];
|
|
42066
|
+
}
|
|
42067
|
+
const firstAvailable = Math.max(columns - visibleLength(firstLinePadding) - visibleLength(endPadding), 1);
|
|
42068
|
+
const continuationAvailable = Math.max(
|
|
42069
|
+
columns - visibleLength(continuationPadding) - visibleLength(endPadding),
|
|
42070
|
+
1
|
|
42071
|
+
);
|
|
42072
|
+
const lines2 = [];
|
|
42073
|
+
let remaining = text2;
|
|
42074
|
+
let isFirstLine = true;
|
|
42075
|
+
while (remaining.length > 0) {
|
|
42076
|
+
const padding = isFirstLine ? firstLinePadding : continuationPadding;
|
|
42077
|
+
const available = isFirstLine ? firstAvailable : continuationAvailable;
|
|
42078
|
+
const chunk = remaining.slice(0, available);
|
|
42079
|
+
lines2.push(padding + chunk + endPadding);
|
|
42080
|
+
remaining = remaining.slice(chunk.length);
|
|
42081
|
+
isFirstLine = false;
|
|
42082
|
+
}
|
|
42083
|
+
return lines2;
|
|
42084
|
+
}
|
|
42085
|
+
function renderPaddedLine(text2, initialPadding, endPadding, columns) {
|
|
42086
|
+
const available = Math.max(columns - visibleLength(initialPadding) - visibleLength(endPadding), 0);
|
|
42087
|
+
const truncated = visibleLength(text2) <= available ? text2 : text2.slice(0, available);
|
|
42088
|
+
const padding = Math.max(available - visibleLength(truncated), 0);
|
|
42089
|
+
return initialPadding + truncated + " ".repeat(padding) + endPadding;
|
|
42090
|
+
}
|
|
42091
|
+
function mergeHighlightRanges(ranges) {
|
|
42092
|
+
const sorted = ranges.filter((range2) => range2.end > range2.start).slice().sort((a, b) => a.start - b.start);
|
|
42093
|
+
const merged = [];
|
|
42094
|
+
for (const range2 of sorted) {
|
|
42095
|
+
const previous = merged[merged.length - 1];
|
|
42096
|
+
if (!previous || range2.start > previous.end) {
|
|
42097
|
+
merged.push(range2);
|
|
42098
|
+
continue;
|
|
42099
|
+
}
|
|
42100
|
+
merged[merged.length - 1] = {
|
|
42101
|
+
start: previous.start,
|
|
42102
|
+
end: Math.max(previous.end, range2.end)
|
|
42103
|
+
};
|
|
42104
|
+
}
|
|
42105
|
+
return merged;
|
|
42106
|
+
}
|
|
42107
|
+
function stylePreviewLine(line, lineStart, ranges) {
|
|
42108
|
+
if (line.length === 0) {
|
|
42109
|
+
return "";
|
|
42110
|
+
}
|
|
42111
|
+
const lineEnd = lineStart + line.length;
|
|
42112
|
+
let cursor = 0;
|
|
42113
|
+
let rendered = "";
|
|
42114
|
+
for (const range2 of ranges) {
|
|
42115
|
+
const start = Math.max(range2.start, lineStart);
|
|
42116
|
+
const end = Math.min(range2.end, lineEnd);
|
|
42117
|
+
if (end <= start) {
|
|
42118
|
+
continue;
|
|
42119
|
+
}
|
|
42120
|
+
const startIndex = start - lineStart;
|
|
42121
|
+
const endIndex = end - lineStart;
|
|
42122
|
+
if (cursor < startIndex) {
|
|
42123
|
+
rendered += ansi(line.slice(cursor, startIndex), DIM);
|
|
42124
|
+
}
|
|
42125
|
+
rendered += ansi(line.slice(startIndex, endIndex), UNDERLINE);
|
|
42126
|
+
cursor = endIndex;
|
|
42127
|
+
}
|
|
42128
|
+
if (cursor < line.length) {
|
|
42129
|
+
rendered += ansi(line.slice(cursor), DIM);
|
|
42130
|
+
}
|
|
42131
|
+
return rendered;
|
|
42132
|
+
}
|
|
42133
|
+
function wrapSourceLine(line, lineStart, availableWidth) {
|
|
42134
|
+
if (line.length === 0) {
|
|
42135
|
+
return [{ text: "", start: lineStart, end: lineStart }];
|
|
42136
|
+
}
|
|
42137
|
+
const width = Math.max(availableWidth, 1);
|
|
42138
|
+
const wrapped = [];
|
|
42139
|
+
let offset = 0;
|
|
42140
|
+
while (offset < line.length) {
|
|
42141
|
+
const text2 = line.slice(offset, offset + width);
|
|
42142
|
+
wrapped.push({
|
|
42143
|
+
text: text2,
|
|
42144
|
+
start: lineStart + offset,
|
|
42145
|
+
end: lineStart + offset + text2.length
|
|
42146
|
+
});
|
|
42147
|
+
offset += text2.length;
|
|
42148
|
+
}
|
|
42149
|
+
return wrapped;
|
|
42150
|
+
}
|
|
42151
|
+
function wrapPreviewSourceText(sourceText, columns) {
|
|
42152
|
+
const availableWidth = Math.max(columns - 2, 1);
|
|
42153
|
+
const logicalLines = sourceText.split("\n");
|
|
42154
|
+
const wrapped = [];
|
|
42155
|
+
let offset = 0;
|
|
42156
|
+
for (const line of logicalLines) {
|
|
42157
|
+
for (const wrappedLine of wrapSourceLine(line, offset, availableWidth)) {
|
|
42158
|
+
wrapped.push(wrappedLine);
|
|
42159
|
+
}
|
|
42160
|
+
offset += line.length + 1;
|
|
42161
|
+
}
|
|
42162
|
+
return wrapped;
|
|
42163
|
+
}
|
|
42164
|
+
function renderPreviewSourceText(sourceText, diagnostics3, columns) {
|
|
42165
|
+
const ranges = mergeHighlightRanges(diagnostics3.map((diagnostic) => ({
|
|
42166
|
+
start: diagnostic.start,
|
|
42167
|
+
end: diagnostic.end
|
|
42168
|
+
})));
|
|
42169
|
+
return wrapPreviewSourceText(sourceText, columns).map((line) => {
|
|
42170
|
+
const rendered = stylePreviewLine(line.text, line.start, ranges);
|
|
42171
|
+
return CURSOR_TO_0 + renderPaddedLine(rendered, " ", "", columns);
|
|
42172
|
+
});
|
|
42173
|
+
}
|
|
42174
|
+
function renderPreviewMessages(diagnostics3, columns) {
|
|
42175
|
+
const messages = Array.from(new Set(diagnostics3.map((diagnostic) => diagnostic.text)));
|
|
42176
|
+
return messages.flatMap((message) => {
|
|
42177
|
+
let isFirstBlock = true;
|
|
42178
|
+
return message.split("\n").flatMap((line) => {
|
|
42179
|
+
const wrappedLines = wrapListItemText(line, isFirstBlock ? "- " : " ", " ", "", columns);
|
|
42180
|
+
isFirstBlock = false;
|
|
42181
|
+
return wrappedLines.map((wrappedLine) => CURSOR_TO_0 + ansi(wrappedLine, DIM + ITALIC));
|
|
42182
|
+
});
|
|
42183
|
+
});
|
|
42184
|
+
}
|
|
42185
|
+
function renderDivider(columns, legendText) {
|
|
42186
|
+
if (legendText === void 0) {
|
|
42187
|
+
return ansi("\u2500".repeat(Math.max(columns, 0)), DIM);
|
|
42188
|
+
}
|
|
42189
|
+
const legend = ` ${legendText} `;
|
|
42190
|
+
const legendLength = visibleLength(legend);
|
|
42191
|
+
if (columns <= legendLength) {
|
|
42192
|
+
return ansi(legend.slice(0, Math.max(columns, 0)), DIM);
|
|
42193
|
+
}
|
|
42194
|
+
const remaining = columns - legendLength;
|
|
42195
|
+
const left = "\u2500".repeat(Math.floor(remaining / 2));
|
|
42196
|
+
const right = "\u2500".repeat(remaining - left.length);
|
|
42197
|
+
return ansi(left + legend + right, DIM);
|
|
42198
|
+
}
|
|
42199
|
+
function renderSelectedRuleDivider(selected, severities, columns) {
|
|
42200
|
+
if (!selected) {
|
|
42201
|
+
return renderDivider(columns);
|
|
42202
|
+
}
|
|
42203
|
+
const currentSeverity = severities[selected.name] ?? selected.defaultSeverity;
|
|
42204
|
+
const text2 = `${selected.name} (currently set as ${getSeverityShortName(currentSeverity)})`;
|
|
42205
|
+
return renderDivider(columns, text2);
|
|
42206
|
+
}
|
|
42207
|
+
function matchesSearch(entry, searchText) {
|
|
42208
|
+
if (searchText.length === 0) {
|
|
42209
|
+
return true;
|
|
42210
|
+
}
|
|
42211
|
+
const normalized = searchText.toLowerCase();
|
|
42212
|
+
return entry.name.toLowerCase().includes(normalized) || entry.description.toLowerCase().includes(normalized) || entry.previewSourceText.toLowerCase().includes(normalized);
|
|
42213
|
+
}
|
|
42214
|
+
function getFilteredEntries(entries2, searchText) {
|
|
42215
|
+
return entries2.filter((entry) => matchesSearch(entry, searchText));
|
|
42216
|
+
}
|
|
42217
|
+
function normalizeStartIndex(length, startIndex) {
|
|
42218
|
+
if (length <= 0) {
|
|
42219
|
+
return 0;
|
|
42220
|
+
}
|
|
42221
|
+
return (startIndex % length + length) % length;
|
|
42222
|
+
}
|
|
42223
|
+
function isPrintableInput(input) {
|
|
42224
|
+
const printablePattern = new RegExp(String.raw`^[^\u0000-\u001F\u007F]+$`, "u");
|
|
42225
|
+
return !input.key.ctrl && !input.key.meta && input.input !== void 0 && input.input.length > 0 && printablePattern.test(input.input);
|
|
42226
|
+
}
|
|
42227
|
+
function buildVisibleEntries(entries2, severities, startIndex, columns) {
|
|
42228
|
+
if (entries2.length === 0) {
|
|
42229
|
+
return {
|
|
42230
|
+
fullLine: renderPaddedLine(ansi("No matching rules", DIM), " ", " ", columns),
|
|
42231
|
+
visibleRules: []
|
|
42232
|
+
};
|
|
42233
|
+
}
|
|
42234
|
+
const reservedColumns = 4;
|
|
42235
|
+
const itemColumns = Math.max(columns - reservedColumns, 0);
|
|
42236
|
+
const separator = " ";
|
|
42237
|
+
const visibleEntries = [];
|
|
42238
|
+
const visibleRules = [];
|
|
42239
|
+
let currentLength = 0;
|
|
42240
|
+
let seenCount = 0;
|
|
42241
|
+
while (seenCount < entries2.length) {
|
|
42242
|
+
const index = (startIndex + seenCount) % entries2.length;
|
|
42243
|
+
const rule = entries2[index];
|
|
42244
|
+
const currentSeverity = severities[rule.name] ?? rule.defaultSeverity;
|
|
42245
|
+
const entry = renderEntry(rule, currentSeverity, seenCount === 0);
|
|
42246
|
+
const nextLength = visibleEntries.length === 0 ? visibleLength(entry) : currentLength + separator.length + visibleLength(entry);
|
|
42247
|
+
if (nextLength > itemColumns) {
|
|
42248
|
+
break;
|
|
42249
|
+
}
|
|
42250
|
+
visibleEntries.push(entry);
|
|
42251
|
+
visibleRules.push(rule);
|
|
42252
|
+
currentLength = nextLength;
|
|
42253
|
+
seenCount++;
|
|
42254
|
+
}
|
|
42255
|
+
const leftMarker = entries2.length > 1 ? ansi("\u2190", DIM) : " ";
|
|
42256
|
+
const rightMarker = entries2.length > 1 ? ansi("\u2192", DIM) : " ";
|
|
42257
|
+
return {
|
|
42258
|
+
fullLine: renderPaddedLine(visibleEntries.join(separator), `${leftMarker} `, ` ${rightMarker}`, columns),
|
|
42259
|
+
visibleRules
|
|
42260
|
+
};
|
|
42261
|
+
}
|
|
42262
|
+
function buildGroupLine(selected, columns) {
|
|
42263
|
+
if (!selected) {
|
|
42264
|
+
return renderPaddedLine("", " ", " ", columns);
|
|
42265
|
+
}
|
|
42266
|
+
const selectedIndex = diagnosticGroups.findIndex((group) => group.id === selected.group);
|
|
42267
|
+
const rotatedGroups = diagnosticGroups.map(
|
|
42268
|
+
(_, index) => diagnosticGroups[(selectedIndex + index) % diagnosticGroups.length]
|
|
42269
|
+
);
|
|
42270
|
+
const content = rotatedGroups.map((group, index) => {
|
|
42271
|
+
const label = group.name.toUpperCase();
|
|
42272
|
+
return index === 0 ? ansi(label, BOLD) : ansi(label, DIM);
|
|
42273
|
+
}).join(" ");
|
|
42274
|
+
return renderPaddedLine(content, " ", " ", columns);
|
|
42275
|
+
}
|
|
42276
|
+
function buildFrame(entries2, searchText, severities, startIndex, columns) {
|
|
42277
|
+
const visible = buildVisibleEntries(entries2, severities, startIndex, columns);
|
|
42278
|
+
const selected = visible.visibleRules[0];
|
|
42279
|
+
const wrappedDescription = wrapPaddedText(selected?.description ?? "", " ", "", columns);
|
|
42280
|
+
const previewLines = renderPreviewSourceText(
|
|
42281
|
+
selected?.previewSourceText ?? "",
|
|
42282
|
+
selected?.previewDiagnostics ?? [],
|
|
42283
|
+
columns
|
|
42284
|
+
);
|
|
42285
|
+
const previewMessages = renderPreviewMessages(selected?.previewDiagnostics ?? [], columns);
|
|
42286
|
+
const previewSectionLines = [...previewLines, ...previewMessages];
|
|
42287
|
+
const paddingLines = Array.from(
|
|
42288
|
+
{ length: Math.max(MIN_PREVIEW_AND_MESSAGES_LINES - previewSectionLines.length, 0) },
|
|
42289
|
+
() => ""
|
|
42290
|
+
);
|
|
42291
|
+
return [
|
|
42292
|
+
CURSOR_HIDE + renderSelectedRuleDivider(selected, severities, columns),
|
|
42293
|
+
...previewSectionLines,
|
|
42294
|
+
...paddingLines,
|
|
42295
|
+
CURSOR_TO_0 + renderDivider(columns),
|
|
42296
|
+
CURSOR_TO_0 + buildGroupLine(selected, columns),
|
|
42297
|
+
CURSOR_TO_0 + visible.fullLine,
|
|
42298
|
+
...wrappedDescription.map((line) => CURSOR_TO_0 + ansi(line, DIM)),
|
|
42299
|
+
CURSOR_TO_0 + renderDivider(columns, getControlsLegend(searchText)),
|
|
42300
|
+
""
|
|
42301
|
+
];
|
|
42302
|
+
}
|
|
42303
|
+
function buildState(entries2, startIndex, searchText, severities) {
|
|
40676
42304
|
return gen2(function* () {
|
|
40677
42305
|
const terminal = yield* Terminal;
|
|
40678
|
-
const columns = yield* terminal.columns;
|
|
40679
|
-
const
|
|
40680
|
-
const
|
|
40681
|
-
const
|
|
40682
|
-
|
|
40683
|
-
|
|
42306
|
+
const columns = Math.max(yield* terminal.columns, 1);
|
|
42307
|
+
const filteredEntries = getFilteredEntries(entries2, searchText);
|
|
42308
|
+
const normalizedStartIndex = normalizeStartIndex(filteredEntries.length, startIndex);
|
|
42309
|
+
const renderedLines = buildFrame(filteredEntries, searchText, severities, normalizedStartIndex, columns);
|
|
42310
|
+
return {
|
|
42311
|
+
startIndex: normalizedStartIndex,
|
|
42312
|
+
searchText,
|
|
42313
|
+
severities,
|
|
42314
|
+
renderedColumns: columns,
|
|
42315
|
+
renderedLines
|
|
42316
|
+
};
|
|
40684
42317
|
});
|
|
40685
42318
|
}
|
|
40686
|
-
function
|
|
40687
|
-
return (
|
|
40688
|
-
|
|
40689
|
-
|
|
40690
|
-
|
|
40691
|
-
|
|
40692
|
-
|
|
42319
|
+
function renderSubmission(state, entries2) {
|
|
42320
|
+
return succeed6(
|
|
42321
|
+
CURSOR_TO_0 + JSON.stringify(
|
|
42322
|
+
Object.fromEntries(
|
|
42323
|
+
entries2.flatMap((entry) => {
|
|
42324
|
+
const severity = state.severities[entry.name];
|
|
42325
|
+
return severity !== void 0 && severity !== entry.defaultSeverity ? [[entry.name, severity]] : [];
|
|
42326
|
+
})
|
|
42327
|
+
)
|
|
42328
|
+
) + "\n"
|
|
42329
|
+
);
|
|
42330
|
+
}
|
|
42331
|
+
function handleProcess(entries2) {
|
|
42332
|
+
return (input, state) => {
|
|
42333
|
+
const filteredEntries = getFilteredEntries(entries2, state.searchText);
|
|
42334
|
+
switch (input.key.name) {
|
|
42335
|
+
case "backspace":
|
|
42336
|
+
return buildState(entries2, 0, state.searchText.slice(0, -1), state.severities).pipe(
|
|
42337
|
+
map6((nextState) => Action2.NextFrame({ state: nextState }))
|
|
42338
|
+
);
|
|
42339
|
+
case "left":
|
|
42340
|
+
if (filteredEntries.length === 0) return succeed6(Action2.Beep());
|
|
42341
|
+
return buildState(entries2, state.startIndex - 1, state.searchText, state.severities).pipe(
|
|
42342
|
+
map6((nextState) => Action2.NextFrame({ state: nextState }))
|
|
42343
|
+
);
|
|
42344
|
+
case "right":
|
|
42345
|
+
if (filteredEntries.length === 0) return succeed6(Action2.Beep());
|
|
42346
|
+
return buildState(entries2, state.startIndex + 1, state.searchText, state.severities).pipe(
|
|
42347
|
+
map6((nextState) => Action2.NextFrame({ state: nextState }))
|
|
42348
|
+
);
|
|
42349
|
+
case "up":
|
|
42350
|
+
case "down":
|
|
42351
|
+
if (filteredEntries.length === 0) return succeed6(Action2.Beep());
|
|
42352
|
+
return buildState(entries2, state.startIndex, state.searchText, {
|
|
42353
|
+
...state.severities,
|
|
42354
|
+
[filteredEntries[state.startIndex].name]: cycleSeverity(
|
|
42355
|
+
state.severities[filteredEntries[state.startIndex].name] ?? filteredEntries[state.startIndex].defaultSeverity,
|
|
42356
|
+
input.key.name === "up" ? "left" : "right"
|
|
42357
|
+
)
|
|
42358
|
+
}).pipe(map6((nextState) => Action2.NextFrame({ state: nextState })));
|
|
42359
|
+
case "enter":
|
|
42360
|
+
case "return":
|
|
42361
|
+
return succeed6(Action2.Submit({
|
|
42362
|
+
value: Object.fromEntries(
|
|
42363
|
+
entries2.flatMap((entry) => {
|
|
42364
|
+
const severity = state.severities[entry.name];
|
|
42365
|
+
return severity !== void 0 && severity !== entry.defaultSeverity ? [[entry.name, severity]] : [];
|
|
42366
|
+
})
|
|
42367
|
+
)
|
|
42368
|
+
}));
|
|
42369
|
+
default:
|
|
42370
|
+
if (!isPrintableInput(input)) return succeed6(Action2.Beep());
|
|
42371
|
+
return buildState(entries2, 0, state.searchText + input.input, state.severities).pipe(
|
|
42372
|
+
map6((nextState) => Action2.NextFrame({ state: nextState }))
|
|
42373
|
+
);
|
|
42374
|
+
}
|
|
40693
42375
|
};
|
|
40694
42376
|
}
|
|
42377
|
+
function getPromptEntries(diagnostics3) {
|
|
42378
|
+
const diagnosticsByName = new Map(diagnostics3.map((diagnostic) => [diagnostic.name, diagnostic]));
|
|
42379
|
+
return diagnostics3.flatMap((rule) => {
|
|
42380
|
+
const diagnostic = diagnosticsByName.get(rule.name);
|
|
42381
|
+
if (!diagnostic) {
|
|
42382
|
+
return [];
|
|
42383
|
+
}
|
|
42384
|
+
return [{
|
|
42385
|
+
name: rule.name,
|
|
42386
|
+
group: diagnostic.group,
|
|
42387
|
+
description: diagnostic.description,
|
|
42388
|
+
previewSourceText: diagnostic.preview.sourceText,
|
|
42389
|
+
previewDiagnostics: diagnostic.preview.diagnostics,
|
|
42390
|
+
defaultSeverity: diagnostic.defaultSeverity
|
|
42391
|
+
}];
|
|
42392
|
+
});
|
|
42393
|
+
}
|
|
40695
42394
|
function createDiagnosticPrompt(diagnostics3, initialSeverities) {
|
|
40696
|
-
const
|
|
40697
|
-
|
|
40698
|
-
|
|
40699
|
-
|
|
40700
|
-
|
|
40701
|
-
|
|
40702
|
-
|
|
40703
|
-
|
|
40704
|
-
|
|
40705
|
-
|
|
40706
|
-
|
|
40707
|
-
|
|
40708
|
-
|
|
42395
|
+
const entries2 = getPromptEntries(diagnostics3);
|
|
42396
|
+
return custom(buildState(entries2, 0, "", initialSeverities), {
|
|
42397
|
+
render: (state, action2) => {
|
|
42398
|
+
switch (action2._tag) {
|
|
42399
|
+
case "Beep":
|
|
42400
|
+
return succeed6(BEEP);
|
|
42401
|
+
case "NextFrame":
|
|
42402
|
+
return succeed6(action2.state.renderedLines.join("\n"));
|
|
42403
|
+
case "Submit":
|
|
42404
|
+
return renderSubmission(state, entries2);
|
|
42405
|
+
}
|
|
42406
|
+
},
|
|
42407
|
+
process: handleProcess(entries2),
|
|
42408
|
+
clear: (state) => gen2(function* () {
|
|
42409
|
+
const terminal = yield* Terminal;
|
|
42410
|
+
const columns = yield* terminal.columns;
|
|
42411
|
+
return eraseRenderedLines(state.renderedLines, columns);
|
|
42412
|
+
})
|
|
40709
42413
|
});
|
|
40710
42414
|
}
|
|
40711
42415
|
|