resultar-check 1.0.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.
@@ -0,0 +1,1030 @@
1
+ let node_child_process = require("node:child_process");
2
+ let node_fs = require("node:fs");
3
+ let node_module = require("node:module");
4
+ let node_path = require("node:path");
5
+ //#region src/result-usage-core.ts
6
+ const resultTypeNames = new Set([
7
+ "DisposableResult",
8
+ "DisposableResultAsync",
9
+ "ErrResult",
10
+ "OkResult",
11
+ "Result",
12
+ "ResultAsync",
13
+ "StrictResult",
14
+ "StrictResultAsync"
15
+ ]);
16
+ const consumerMethods = new Set([
17
+ "_unsafeUnwrap",
18
+ "_unsafeUnwrapErr",
19
+ "isErr",
20
+ "isOk",
21
+ "match",
22
+ "matchTags",
23
+ "matchTagsPartial",
24
+ "unwrapOr",
25
+ "unwrapOrThrow"
26
+ ]);
27
+ const consumerProperties = new Set(["error", "value"]);
28
+ const getUnionOrIntersectionTypes$1 = (tsApi, type) => (type.flags & (tsApi.TypeFlags.Union | tsApi.TypeFlags.Intersection)) === 0 ? void 0 : type.types ?? [];
29
+ const getSymbolName = (symbol) => {
30
+ if (symbol === void 0) return;
31
+ if (typeof symbol.getName === "function") return symbol.getName();
32
+ const { escapedName } = symbol;
33
+ return typeof escapedName === "string" ? escapedName : void 0;
34
+ };
35
+ const getTypeSymbolName = (type) => {
36
+ if (typeof type.getSymbol === "function") return getSymbolName(type.getSymbol());
37
+ return getSymbolName(type.symbol);
38
+ };
39
+ const getTokenPosOfNode$1 = (tsApi, node, sourceFile) => {
40
+ const getTokenPos = tsApi.getTokenPosOfNode;
41
+ return getTokenPos === void 0 ? node.pos : getTokenPos(node, sourceFile);
42
+ };
43
+ const getNodeStart$1 = (tsApi, node, sourceFile) => typeof node.getStart === "function" ? node.getStart(sourceFile) : getTokenPosOfNode$1(tsApi, node, sourceFile);
44
+ const getIdentifierText$1 = (identifier) => {
45
+ if (typeof identifier.text === "string") return identifier.text;
46
+ return identifier.escapedText === void 0 ? "" : String(identifier.escapedText);
47
+ };
48
+ const getNodeWidth$1 = (tsApi, node, sourceFile) => {
49
+ if (typeof node.getWidth === "function") return node.getWidth(sourceFile);
50
+ const start = getNodeStart$1(tsApi, node, sourceFile);
51
+ return node.end - start;
52
+ };
53
+ const getEntityNameText = (tsApi, name) => tsApi.isIdentifier(name) ? getIdentifierText$1(name) : `${getEntityNameText(tsApi, name.left)}.${getIdentifierText$1(name.right)}`;
54
+ const normalizeNoDiscardMode = (mode) => mode === "direct" ? "direct" : "must-use";
55
+ const isResultLikeType = (tsApi, checker, node, type) => {
56
+ const unionOrIntersectionTypes = getUnionOrIntersectionTypes$1(tsApi, type);
57
+ if (unionOrIntersectionTypes !== void 0) return unionOrIntersectionTypes.some((innerType) => isResultLikeType(tsApi, checker, node, innerType));
58
+ const aliasName = getSymbolName(type.aliasSymbol);
59
+ const symbolName = getTypeSymbolName(type);
60
+ if (aliasName !== void 0 && resultTypeNames.has(aliasName) || symbolName !== void 0 && resultTypeNames.has(symbolName)) return true;
61
+ if (tsApi.isTypeReferenceNode(node)) {
62
+ const typeName = getEntityNameText(tsApi, node.typeName);
63
+ const unqualifiedName = typeName.includes(".") ? typeName.split(".").at(-1) : typeName;
64
+ return unqualifiedName !== void 0 && resultTypeNames.has(unqualifiedName);
65
+ }
66
+ return false;
67
+ };
68
+ const unwrapExpression = (tsApi, expression) => {
69
+ let current = expression;
70
+ for (;;) if (tsApi.isParenthesizedExpression(current)) current = current.expression;
71
+ else if (tsApi.isAsExpression(current)) current = current.expression;
72
+ else if (tsApi.isTypeAssertionExpression(current)) current = current.expression;
73
+ else if (tsApi.isNonNullExpression(current)) current = current.expression;
74
+ else if (tsApi.isSatisfiesExpression(current)) current = current.expression;
75
+ else return current;
76
+ };
77
+ const isExplicitDiscard = (tsApi, expression) => tsApi.isVoidExpression(unwrapExpression(tsApi, expression));
78
+ const isCallLikeDiscard = (tsApi, expression) => {
79
+ const unwrapped = unwrapExpression(tsApi, expression);
80
+ if (tsApi.isAwaitExpression(unwrapped)) return isCallLikeDiscard(tsApi, unwrapped.expression);
81
+ if (tsApi.isCallExpression(unwrapped)) return true;
82
+ if (tsApi.isConditionalExpression(unwrapped)) return isCallLikeDiscard(tsApi, unwrapped.whenTrue) || isCallLikeDiscard(tsApi, unwrapped.whenFalse);
83
+ if (tsApi.isBinaryExpression(unwrapped)) {
84
+ const { kind } = unwrapped.operatorToken;
85
+ return (kind === tsApi.SyntaxKind.AmpersandAmpersandToken || kind === tsApi.SyntaxKind.BarBarToken || kind === tsApi.SyntaxKind.QuestionQuestionToken) && isCallLikeDiscard(tsApi, unwrapped.right);
86
+ }
87
+ return false;
88
+ };
89
+ const getTypeName$1 = (context, node, type) => context.checker.typeToString(type, node, context.tsApi.TypeFormatFlags.NoTruncation);
90
+ const createFinding$1 = (context, node, typeName, message) => {
91
+ const start = getNodeStart$1(context.tsApi, node, context.sourceFile);
92
+ const position = context.tsApi.getLineAndCharacterOfPosition(context.sourceFile, start);
93
+ return {
94
+ column: position.character + 1,
95
+ file: context.sourceFile.fileName,
96
+ length: getNodeWidth$1(context.tsApi, node, context.sourceFile),
97
+ line: position.line + 1,
98
+ message,
99
+ rule: "no-discard",
100
+ severity: "error",
101
+ start,
102
+ type: typeName
103
+ };
104
+ };
105
+ const createIgnoredFinding = (context, node, typeName) => createFinding$1(context, node, typeName, `Ignored ${typeName} value. Handle it or explicitly discard it with \`void\`.`);
106
+ const createUnhandledFinding = (context, tracked) => createFinding$1(context, tracked.identifier, tracked.typeName, `Unhandled ${tracked.typeName} value assigned to \`${tracked.name}\`. Handle it, return it, or explicitly discard it with \`void\`.`);
107
+ const symbolsEqual = (left, right) => left === right || left.valueDeclaration === right.valueDeclaration;
108
+ const isWrapperParent = (tsApi, parent, child) => tsApi.isParenthesizedExpression(parent) && parent.expression === child || tsApi.isAsExpression(parent) && parent.expression === child || tsApi.isTypeAssertionExpression(parent) && parent.expression === child || tsApi.isNonNullExpression(parent) && parent.expression === child || tsApi.isSatisfiesExpression(parent) && parent.expression === child;
109
+ const isReturnValueContainerParent = (tsApi, parent, child) => isWrapperParent(tsApi, parent, child) || tsApi.isShorthandPropertyAssignment(parent) && parent.name === child || tsApi.isPropertyAssignment(parent) && parent.initializer === child || tsApi.isSpreadAssignment(parent) && parent.expression === child || tsApi.isSpreadElement(parent) && parent.expression === child || tsApi.isObjectLiteralExpression(parent) && parent.properties.some((property) => property === child) || tsApi.isArrayLiteralExpression(parent) && parent.elements.some((element) => element === child) || tsApi.isConditionalExpression(parent) && (parent.whenTrue === child || parent.whenFalse === child);
110
+ const getReferenceChainRoot = (tsApi, identifier, ancestors) => {
111
+ let current = identifier;
112
+ let parentIndex = ancestors.length - 1;
113
+ for (;;) {
114
+ const parent = ancestors[parentIndex];
115
+ if (parent === void 0) return {
116
+ parent: void 0,
117
+ root: current
118
+ };
119
+ if (isWrapperParent(tsApi, parent, current)) current = parent;
120
+ else if (tsApi.isAwaitExpression(parent) && parent.expression === current) current = parent;
121
+ else if (tsApi.isPropertyAccessExpression(parent) && parent.expression === current) current = parent;
122
+ else if (tsApi.isCallExpression(parent) && parent.expression === current) current = parent;
123
+ else return {
124
+ parent,
125
+ root: current
126
+ };
127
+ parentIndex -= 1;
128
+ }
129
+ };
130
+ const isReturnedReference = (tsApi, identifier, ancestors) => {
131
+ const { parent, root } = getReferenceChainRoot(tsApi, identifier, ancestors);
132
+ if (parent !== void 0 && tsApi.isReturnStatement(parent) && parent.expression === root || parent !== void 0 && tsApi.isArrowFunction(parent) && parent.body === root) return true;
133
+ let current = identifier;
134
+ for (let parentIndex = ancestors.length - 1; parentIndex >= 0; parentIndex -= 1) {
135
+ const containerParent = ancestors[parentIndex];
136
+ if (containerParent === void 0) return false;
137
+ if (isReturnValueContainerParent(tsApi, containerParent, current)) {
138
+ current = containerParent;
139
+ continue;
140
+ }
141
+ return tsApi.isReturnStatement(containerParent) && containerParent.expression === current || tsApi.isArrowFunction(containerParent) && containerParent.body === current;
142
+ }
143
+ return false;
144
+ };
145
+ const isExplicitDiscardReference = (tsApi, identifier, ancestors) => {
146
+ const { parent, root } = getReferenceChainRoot(tsApi, identifier, ancestors);
147
+ return parent !== void 0 && tsApi.isVoidExpression(parent) && parent.expression === root;
148
+ };
149
+ const isConsumedByReceiverChain = (tsApi, identifier, ancestors) => {
150
+ let current = identifier;
151
+ let parentIndex = ancestors.length - 1;
152
+ for (;;) {
153
+ const parent = ancestors[parentIndex];
154
+ if (parent === void 0) return false;
155
+ if (isWrapperParent(tsApi, parent, current)) current = parent;
156
+ else if (tsApi.isAwaitExpression(parent) && parent.expression === current) current = parent;
157
+ else if (tsApi.isPropertyAccessExpression(parent) && parent.expression === current) {
158
+ const methodOrPropertyName = getIdentifierText$1(parent.name);
159
+ const nextParent = ancestors[parentIndex - 1];
160
+ if (consumerProperties.has(methodOrPropertyName)) return true;
161
+ if (consumerMethods.has(methodOrPropertyName) && nextParent !== void 0 && tsApi.isCallExpression(nextParent) && nextParent.expression === parent) return true;
162
+ current = parent;
163
+ } else if (tsApi.isCallExpression(parent) && parent.expression === current) current = parent;
164
+ else return false;
165
+ parentIndex -= 1;
166
+ }
167
+ };
168
+ const isHandledReference = (tsApi, identifier, ancestors) => isReturnedReference(tsApi, identifier, ancestors) || isExplicitDiscardReference(tsApi, identifier, ancestors) || isConsumedByReceiverChain(tsApi, identifier, ancestors);
169
+ const isIdentifierInsideDiscardedResultExpression = (context, identifier, ancestors) => {
170
+ let current = identifier;
171
+ for (let index = ancestors.length - 1; index >= 0; index -= 1) {
172
+ current = ancestors[index];
173
+ if (context.tsApi.isExpressionStatement(current)) {
174
+ if (isExplicitDiscard(context.tsApi, current.expression) || !isCallLikeDiscard(context.tsApi, current.expression)) return false;
175
+ const type = context.checker.getTypeAtLocation(current.expression);
176
+ return isResultLikeType(context.tsApi, context.checker, current.expression, type);
177
+ }
178
+ }
179
+ return false;
180
+ };
181
+ const getTrackedResult = (tsApi, checker, trackedResults, identifier) => {
182
+ const shorthandParent = identifier.parent;
183
+ const symbol = shorthandParent !== void 0 && tsApi.isShorthandPropertyAssignment(shorthandParent) && shorthandParent.name === identifier ? checker.getShorthandAssignmentValueSymbol(shorthandParent) : checker.getSymbolAtLocation(identifier);
184
+ return symbol === void 0 ? void 0 : trackedResults.find((tracked) => symbolsEqual(tracked.symbol, symbol));
185
+ };
186
+ const collectTrackedResults = (context) => {
187
+ const trackedResults = [];
188
+ const visit = (node) => {
189
+ if (context.tsApi.isVariableDeclaration(node) && context.tsApi.isIdentifier(node.name) && node.initializer !== void 0 && isCallLikeDiscard(context.tsApi, node.initializer)) {
190
+ const type = context.checker.getTypeAtLocation(node.initializer);
191
+ if (isResultLikeType(context.tsApi, context.checker, node.initializer, type)) {
192
+ const symbol = context.checker.getSymbolAtLocation(node.name);
193
+ if (symbol !== void 0) trackedResults.push({
194
+ declaration: node,
195
+ hasDiscardedResultUse: false,
196
+ handled: false,
197
+ identifier: node.name,
198
+ name: getIdentifierText$1(node.name),
199
+ symbol,
200
+ typeName: getTypeName$1(context, node.initializer, type)
201
+ });
202
+ }
203
+ }
204
+ context.tsApi.forEachChild(node, visit);
205
+ };
206
+ visit(context.sourceFile);
207
+ return trackedResults;
208
+ };
209
+ const markTrackedResultUses = (context, trackedResults) => {
210
+ const visit = (node, ancestors) => {
211
+ if (context.tsApi.isIdentifier(node)) {
212
+ const tracked = getTrackedResult(context.tsApi, context.checker, trackedResults, node);
213
+ if (tracked !== void 0 && node !== tracked.identifier) {
214
+ if (isHandledReference(context.tsApi, node, ancestors)) tracked.handled = true;
215
+ if (isIdentifierInsideDiscardedResultExpression(context, node, ancestors)) tracked.hasDiscardedResultUse = true;
216
+ }
217
+ }
218
+ context.tsApi.forEachChild(node, (child) => visit(child, [...ancestors, node]));
219
+ };
220
+ visit(context.sourceFile, []);
221
+ };
222
+ const getDirectFindings = (context) => {
223
+ const findings = [];
224
+ const visit = (node) => {
225
+ if (context.tsApi.isExpressionStatement(node) && !isExplicitDiscard(context.tsApi, node.expression) && isCallLikeDiscard(context.tsApi, node.expression)) {
226
+ const type = context.checker.getTypeAtLocation(node.expression);
227
+ if (isResultLikeType(context.tsApi, context.checker, node.expression, type)) findings.push(createIgnoredFinding(context, node.expression, getTypeName$1(context, node.expression, type)));
228
+ }
229
+ context.tsApi.forEachChild(node, visit);
230
+ };
231
+ visit(context.sourceFile);
232
+ return findings;
233
+ };
234
+ const getMustUseFindings = (context) => {
235
+ const trackedResults = collectTrackedResults(context);
236
+ markTrackedResultUses(context, trackedResults);
237
+ return trackedResults.filter((tracked) => !tracked.handled && !tracked.hasDiscardedResultUse).map((tracked) => createUnhandledFinding(context, tracked));
238
+ };
239
+ const getSourceFileNoDiscardFindings = (tsApi, checker, sourceFile, options = {}) => {
240
+ const context = {
241
+ checker,
242
+ sourceFile,
243
+ tsApi
244
+ };
245
+ const mode = normalizeNoDiscardMode(options.mode);
246
+ const directFindings = getDirectFindings(context);
247
+ return mode === "must-use" ? [...directFindings, ...getMustUseFindings(context)] : directFindings;
248
+ };
249
+ //#endregion
250
+ //#region src/rules-core.ts
251
+ const resultarRuleNames = [
252
+ "no-discard",
253
+ "prefer-map-err",
254
+ "prefer-and-then",
255
+ "typed-catch-mapper",
256
+ "no-unsafe-await",
257
+ "no-try-catch-in-safe-try",
258
+ "yield-star-in-safe-try",
259
+ "unsafe-result-type-assertion",
260
+ "prefer-tagged-error",
261
+ "tagged-error-name-match",
262
+ "no-tagged-error-constructor-override",
263
+ "no-useless-recovery"
264
+ ];
265
+ const ruleOptionNameByRule = {
266
+ "no-discard": "noDiscard",
267
+ "no-tagged-error-constructor-override": "noTaggedErrorConstructorOverride",
268
+ "no-try-catch-in-safe-try": "noTryCatchInSafeTry",
269
+ "no-unsafe-await": "noUnsafeAwait",
270
+ "no-useless-recovery": "noUselessRecovery",
271
+ "prefer-and-then": "preferAndThen",
272
+ "prefer-map-err": "preferMapErr",
273
+ "prefer-tagged-error": "preferTaggedError",
274
+ "tagged-error-name-match": "taggedErrorNameMatch",
275
+ "typed-catch-mapper": "typedCatchMapper",
276
+ "unsafe-result-type-assertion": "unsafeResultTypeAssertion",
277
+ "yield-star-in-safe-try": "yieldStarInSafeTry"
278
+ };
279
+ const defaultResultarRulesOptions = {
280
+ noDiscard: "error",
281
+ noDiscardMode: "must-use",
282
+ noTaggedErrorConstructorOverride: "warning",
283
+ noTryCatchInSafeTry: "warning",
284
+ noUnsafeAwait: "off",
285
+ noUnsafeAwaitMode: "resultar-context",
286
+ noUselessRecovery: "warning",
287
+ preferAndThen: "warning",
288
+ preferMapErr: "warning",
289
+ preferTaggedError: "warning",
290
+ taggedErrorNameMatch: "warning",
291
+ typedCatchMapper: "warning",
292
+ unsafeResultTypeAssertion: "warning",
293
+ yieldStarInSafeTry: "warning"
294
+ };
295
+ resultarRuleNames.filter((ruleName) => ruleName !== "no-discard");
296
+ const recoveryMethods = new Set([
297
+ "catchReason",
298
+ "catchReasons",
299
+ "catchTag",
300
+ "catchTags",
301
+ "mapErr",
302
+ "orElse",
303
+ "unwrapReason"
304
+ ]);
305
+ const tryMapperCallNames = new Set([
306
+ "fromThrowable",
307
+ "fromThrowableAsync",
308
+ "tryCatch",
309
+ "tryCatchAsync",
310
+ "tryResult",
311
+ "tryResultAsync",
312
+ "tryAsync"
313
+ ]);
314
+ const asyncAwaitBoundaryCallNames = new Set([
315
+ "fromThrowableAsync",
316
+ "tryCatchAsync",
317
+ "tryResultAsync",
318
+ "tryAsync"
319
+ ]);
320
+ const normalizeRuleSeverity = (value, fallback) => value === "error" || value === "message" || value === "off" || value === "suggestion" || value === "warning" ? value : fallback;
321
+ const normalizeNoUnsafeAwaitMode = (value, fallback = defaultResultarRulesOptions.noUnsafeAwaitMode) => value === "all" || value === "resultar-context" ? value : fallback;
322
+ const normalizeResultarRulesOptions = (options = {}) => ({
323
+ noDiscard: normalizeRuleSeverity(options.noDiscard, defaultResultarRulesOptions.noDiscard),
324
+ noDiscardMode: normalizeNoDiscardMode(options.noDiscardMode),
325
+ noTaggedErrorConstructorOverride: normalizeRuleSeverity(options.noTaggedErrorConstructorOverride, defaultResultarRulesOptions.noTaggedErrorConstructorOverride),
326
+ noTryCatchInSafeTry: normalizeRuleSeverity(options.noTryCatchInSafeTry, defaultResultarRulesOptions.noTryCatchInSafeTry),
327
+ noUnsafeAwait: normalizeRuleSeverity(options.noUnsafeAwait, defaultResultarRulesOptions.noUnsafeAwait),
328
+ noUnsafeAwaitMode: normalizeNoUnsafeAwaitMode(options.noUnsafeAwaitMode),
329
+ noUselessRecovery: normalizeRuleSeverity(options.noUselessRecovery, defaultResultarRulesOptions.noUselessRecovery),
330
+ preferAndThen: normalizeRuleSeverity(options.preferAndThen, defaultResultarRulesOptions.preferAndThen),
331
+ preferMapErr: normalizeRuleSeverity(options.preferMapErr, defaultResultarRulesOptions.preferMapErr),
332
+ preferTaggedError: normalizeRuleSeverity(options.preferTaggedError, defaultResultarRulesOptions.preferTaggedError),
333
+ taggedErrorNameMatch: normalizeRuleSeverity(options.taggedErrorNameMatch, defaultResultarRulesOptions.taggedErrorNameMatch),
334
+ typedCatchMapper: normalizeRuleSeverity(options.typedCatchMapper, defaultResultarRulesOptions.typedCatchMapper),
335
+ unsafeResultTypeAssertion: normalizeRuleSeverity(options.unsafeResultTypeAssertion, defaultResultarRulesOptions.unsafeResultTypeAssertion),
336
+ yieldStarInSafeTry: normalizeRuleSeverity(options.yieldStarInSafeTry, defaultResultarRulesOptions.yieldStarInSafeTry)
337
+ });
338
+ const getRuleSeverity = (options, rule) => {
339
+ const severity = options[ruleOptionNameByRule[rule]];
340
+ return severity === "off" ? void 0 : severity;
341
+ };
342
+ const getTokenPosOfNode = (context, node) => {
343
+ const getTokenPos = context.tsApi.getTokenPosOfNode;
344
+ return getTokenPos === void 0 ? node.pos : getTokenPos(node, context.sourceFile);
345
+ };
346
+ const getNodeStart = (context, node) => typeof node.getStart === "function" ? node.getStart(context.sourceFile) : getTokenPosOfNode(context, node);
347
+ const getIdentifierText = (identifier) => {
348
+ if (typeof identifier.text === "string") return identifier.text;
349
+ return identifier.escapedText === void 0 ? "" : String(identifier.escapedText);
350
+ };
351
+ const getNodeWidth = (context, node) => {
352
+ if (typeof node.getWidth === "function") return node.getWidth(context.sourceFile);
353
+ const start = getNodeStart(context, node);
354
+ return node.end - start;
355
+ };
356
+ const createFinding = (context, node, rule, severity, message, type) => {
357
+ const start = getNodeStart(context, node);
358
+ const position = context.tsApi.getLineAndCharacterOfPosition(context.sourceFile, start);
359
+ const base = {
360
+ column: position.character + 1,
361
+ file: context.sourceFile.fileName,
362
+ length: getNodeWidth(context, node),
363
+ line: position.line + 1,
364
+ message,
365
+ rule,
366
+ severity,
367
+ start
368
+ };
369
+ return type === void 0 ? base : {
370
+ ...base,
371
+ type
372
+ };
373
+ };
374
+ const visitSourceFile = (context, visitor) => {
375
+ const visit = (node) => {
376
+ visitor(node);
377
+ context.tsApi.forEachChild(node, visit);
378
+ };
379
+ visit(context.sourceFile);
380
+ };
381
+ const getPropertyNameText = (tsApi, name) => {
382
+ if (tsApi.isIdentifier(name)) return getIdentifierText(name);
383
+ if (tsApi.isStringLiteral(name) || tsApi.isNumericLiteral(name)) return name.text;
384
+ };
385
+ const getExpressionName = (tsApi, expression) => {
386
+ const unwrapped = unwrapExpression(tsApi, expression);
387
+ if (tsApi.isIdentifier(unwrapped)) return getIdentifierText(unwrapped);
388
+ if (tsApi.isPropertyAccessExpression(unwrapped)) return getIdentifierText(unwrapped.name);
389
+ };
390
+ const getMethodCall = (tsApi, node) => {
391
+ if (!tsApi.isCallExpression(node)) return;
392
+ const expression = unwrapExpression(tsApi, node.expression);
393
+ if (!tsApi.isPropertyAccessExpression(expression)) return;
394
+ if (!tsApi.isIdentifier(expression.name)) return;
395
+ return {
396
+ methodName: getIdentifierText(expression.name),
397
+ nameNode: expression.name,
398
+ receiver: expression.expression
399
+ };
400
+ };
401
+ const getTypeName = (context, node, type) => context.checker.typeToString(type, node, context.tsApi.TypeFormatFlags.NoTruncation);
402
+ const getUnionOrIntersectionTypes = (context, type) => (type.flags & (context.tsApi.TypeFlags.Union | context.tsApi.TypeFlags.Intersection)) === 0 ? void 0 : type.types ?? [];
403
+ const getTypeArguments = (context, type) => {
404
+ const { aliasTypeArguments } = type;
405
+ if (aliasTypeArguments !== void 0 && aliasTypeArguments.length > 0) return aliasTypeArguments;
406
+ const reference = type;
407
+ return reference.target === void 0 ? [] : context.checker.getTypeArguments(reference);
408
+ };
409
+ const getResultTypeParts = (context, node, type) => {
410
+ const unionOrIntersectionTypes = getUnionOrIntersectionTypes(context, type);
411
+ if (unionOrIntersectionTypes !== void 0) return unionOrIntersectionTypes.flatMap((innerType) => getResultTypeParts(context, node, innerType));
412
+ if (!isResultLikeType(context.tsApi, context.checker, node, type)) return [];
413
+ const [okType, errorType] = getTypeArguments(context, type);
414
+ return [{
415
+ error: errorType,
416
+ ok: okType
417
+ }];
418
+ };
419
+ const isResultAsyncLikeType = (context, node, type) => {
420
+ const unionOrIntersectionTypes = getUnionOrIntersectionTypes(context, type);
421
+ if (unionOrIntersectionTypes !== void 0) return unionOrIntersectionTypes.some((innerType) => isResultAsyncLikeType(context, node, innerType));
422
+ return /\b(?:DisposableResultAsync|ResultAsync|StrictResultAsync)\b/.test(getTypeName(context, node, type));
423
+ };
424
+ const isResultLikeExpression = (context, expression) => {
425
+ const type = context.checker.getTypeAtLocation(expression);
426
+ return isResultLikeType(context.tsApi, context.checker, expression, type);
427
+ };
428
+ const getUnionTypes = (context, type) => (type.flags & context.tsApi.TypeFlags.Union) === 0 ? void 0 : type.types ?? [];
429
+ const everyUnionPart = (context, type, predicate) => {
430
+ const unionTypes = getUnionTypes(context, type);
431
+ return unionTypes === void 0 ? predicate(type) : unionTypes.every((innerType) => predicate(innerType));
432
+ };
433
+ const isResultAsyncLikeAwaitType = (context, node, type) => everyUnionPart(context, type, (innerType) => isResultAsyncLikeType(context, node, innerType));
434
+ const isResultLikeAwaitedType = (context, node, type) => everyUnionPart(context, type, (innerType) => isResultLikeType(context.tsApi, context.checker, node, innerType));
435
+ const getPromisedTypeOfPromise = (context, node, type) => {
436
+ return context.checker.getPromisedTypeOfPromise?.(type, node);
437
+ };
438
+ const isSafeAwaitExpression = (context, expression) => {
439
+ const expressionType = context.checker.getTypeAtLocation(expression);
440
+ if (isResultAsyncLikeAwaitType(context, expression, expressionType)) return true;
441
+ const promisedType = getPromisedTypeOfPromise(context, expression, expressionType);
442
+ if (promisedType === void 0) return true;
443
+ return isResultLikeAwaitedType(context, expression, context.checker.getAwaitedType(expressionType) ?? promisedType);
444
+ };
445
+ const isPromiseOfResultLikeType = (context, node, type) => {
446
+ const unionOrIntersectionTypes = getUnionOrIntersectionTypes(context, type);
447
+ if (unionOrIntersectionTypes !== void 0) return unionOrIntersectionTypes.some((innerType) => isPromiseOfResultLikeType(context, node, innerType));
448
+ const promisedType = getPromisedTypeOfPromise(context, node, type);
449
+ if (promisedType === void 0) return false;
450
+ return isResultLikeAwaitedType(context, node, context.checker.getAwaitedType(type) ?? promisedType);
451
+ };
452
+ const isResultarAsyncContextReturnType = (context, node, type) => isResultAsyncLikeType(context, node, type) || isPromiseOfResultLikeType(context, node, type);
453
+ const getFunctionLikeReturnType = (context, node) => {
454
+ const signature = context.checker.getSignatureFromDeclaration(node);
455
+ if (signature !== void 0) return signature.getReturnType();
456
+ const [callSignature] = context.checker.getTypeAtLocation(node).getCallSignatures();
457
+ return callSignature?.getReturnType();
458
+ };
459
+ const isResultarAsyncFunctionContext = (context, node) => {
460
+ if (!isFunctionLike(context.tsApi, node)) return false;
461
+ const returnType = getFunctionLikeReturnType(context, node);
462
+ return returnType !== void 0 && isResultarAsyncContextReturnType(context, node, returnType);
463
+ };
464
+ const isUnknownOrAnyType = (tsApi, type) => type !== void 0 && (Boolean(type.flags & tsApi.TypeFlags.Unknown) || Boolean(type.flags & tsApi.TypeFlags.Any));
465
+ const isNeverType = (tsApi, type) => type !== void 0 && Boolean(type.flags & tsApi.TypeFlags.Never);
466
+ const getReturnedExpressions = (tsApi, callback) => {
467
+ const expressions = [];
468
+ if (!tsApi.isArrowFunction(callback) && !tsApi.isFunctionExpression(callback)) return expressions;
469
+ if (tsApi.isArrowFunction(callback) && !tsApi.isBlock(callback.body)) return [callback.body];
470
+ const { body } = callback;
471
+ const visit = (node) => {
472
+ if (node !== body && isFunctionLike(tsApi, node)) return;
473
+ if (tsApi.isReturnStatement(node) && node.expression !== void 0) {
474
+ expressions.push(node.expression);
475
+ return;
476
+ }
477
+ tsApi.forEachChild(node, visit);
478
+ };
479
+ visit(body);
480
+ return expressions;
481
+ };
482
+ const isFunctionLike = (tsApi, node) => tsApi.isArrowFunction(node) || tsApi.isFunctionDeclaration(node) || tsApi.isFunctionExpression(node) || tsApi.isMethodDeclaration(node);
483
+ const isErrConstructorCall = (tsApi, expression) => {
484
+ const unwrapped = unwrapExpression(tsApi, expression);
485
+ if (!tsApi.isCallExpression(unwrapped)) return false;
486
+ return getExpressionName(tsApi, unwrapped.expression) === "err";
487
+ };
488
+ const hasObjectCatchProperty = (tsApi, expression) => {
489
+ const unwrapped = unwrapExpression(tsApi, expression);
490
+ if (!tsApi.isObjectLiteralExpression(unwrapped)) return false;
491
+ return unwrapped.properties.some((property) => {
492
+ if (tsApi.isPropertyAssignment(property) || tsApi.isMethodDeclaration(property)) return getPropertyNameText(tsApi, property.name) === "catch";
493
+ return false;
494
+ });
495
+ };
496
+ const hasMapperArgument = (tsApi, call) => {
497
+ const [firstArgument, secondArgument] = call.arguments;
498
+ return secondArgument !== void 0 || firstArgument !== void 0 && hasObjectCatchProperty(tsApi, firstArgument);
499
+ };
500
+ const getObjectTryBody = (tsApi, objectLiteral) => {
501
+ for (const property of objectLiteral.properties) {
502
+ if (tsApi.isMethodDeclaration(property) && getPropertyNameText(tsApi, property.name) === "try") return property;
503
+ if (tsApi.isPropertyAssignment(property) && getPropertyNameText(tsApi, property.name) === "try" && (tsApi.isArrowFunction(property.initializer) || tsApi.isFunctionExpression(property.initializer))) return property.initializer;
504
+ }
505
+ };
506
+ const getSafeTryBody = (tsApi, call) => {
507
+ if (getExpressionName(tsApi, call.expression) !== "safeTry") return;
508
+ const [firstArgument] = call.arguments;
509
+ if (firstArgument === void 0) return;
510
+ const unwrapped = unwrapExpression(tsApi, firstArgument);
511
+ if (tsApi.isArrowFunction(unwrapped) || tsApi.isFunctionExpression(unwrapped)) return unwrapped;
512
+ if (!tsApi.isObjectLiteralExpression(unwrapped)) return;
513
+ return getObjectTryBody(tsApi, unwrapped);
514
+ };
515
+ const getResultarAwaitBoundaryBody = (tsApi, call) => {
516
+ const callName = getExpressionName(tsApi, call.expression);
517
+ if (callName === void 0 || !asyncAwaitBoundaryCallNames.has(callName)) return;
518
+ const [firstArgument] = call.arguments;
519
+ if (firstArgument === void 0) return;
520
+ const unwrapped = unwrapExpression(tsApi, firstArgument);
521
+ if (tsApi.isArrowFunction(unwrapped) || tsApi.isFunctionExpression(unwrapped)) return unwrapped;
522
+ if (tsApi.isObjectLiteralExpression(unwrapped)) return getObjectTryBody(tsApi, unwrapped);
523
+ };
524
+ const getResultarAwaitContextBody = (tsApi, call) => getSafeTryBody(tsApi, call) ?? getResultarAwaitBoundaryBody(tsApi, call);
525
+ const visitSafeTryBody = (tsApi, body, visitor) => {
526
+ const root = body.body;
527
+ if (root === void 0) return;
528
+ const visit = (node) => {
529
+ if (node !== root && isFunctionLike(tsApi, node)) return;
530
+ visitor(node);
531
+ tsApi.forEachChild(node, visit);
532
+ };
533
+ visit(root);
534
+ };
535
+ const getCreateTaggedErrorOptions = (tsApi, node) => {
536
+ for (const heritageClause of node.heritageClauses ?? []) {
537
+ if (heritageClause.token !== tsApi.SyntaxKind.ExtendsKeyword) continue;
538
+ for (const heritageType of heritageClause.types) {
539
+ const { expression } = heritageType;
540
+ if (tsApi.isCallExpression(expression) && getExpressionName(tsApi, expression.expression) === "createTaggedError") {
541
+ const [options] = expression.arguments;
542
+ return options !== void 0 && tsApi.isObjectLiteralExpression(options) ? options : void 0;
543
+ }
544
+ }
545
+ }
546
+ };
547
+ const getTaggedErrorName = (tsApi, options) => {
548
+ for (const property of options.properties) if (tsApi.isPropertyAssignment(property) && getPropertyNameText(tsApi, property.name) === "name" && (tsApi.isStringLiteral(property.initializer) || tsApi.isNoSubstitutionTemplateLiteral(property.initializer))) return property.initializer;
549
+ };
550
+ const classExtendsNativeError = (tsApi, node) => (node.heritageClauses ?? []).some((heritageClause) => heritageClause.token === tsApi.SyntaxKind.ExtendsKeyword && heritageClause.types.some((heritageType) => getExpressionName(tsApi, heritageType.expression) === "Error"));
551
+ const getFirstResultErrorTypes = (context, node, type) => getResultTypeParts(context, node, type).map((part) => part.error).filter((errorType) => errorType !== void 0);
552
+ const getPreferMapErrFindings = (context, severity) => {
553
+ const findings = [];
554
+ visitSourceFile(context, (node) => {
555
+ const call = context.tsApi.isCallExpression(node) ? node : void 0;
556
+ const methodCall = getMethodCall(context.tsApi, node);
557
+ if (call === void 0 || methodCall?.methodName !== "orElse") return;
558
+ if (!isResultLikeExpression(context, methodCall.receiver)) return;
559
+ const [callback] = call.arguments;
560
+ const returnedExpressions = callback === void 0 ? [] : getReturnedExpressions(context.tsApi, callback);
561
+ if (returnedExpressions.length === 0 || !returnedExpressions.every((expression) => isErrConstructorCall(context.tsApi, expression) && isResultLikeExpression(context, expression))) return;
562
+ findings.push(createFinding(context, methodCall.nameNode, "prefer-map-err", severity, "`orElse` only replaces the failure with another Err. Use `mapErr` when the Ok value cannot recover."));
563
+ });
564
+ return findings;
565
+ };
566
+ const getPreferAndThenFindings = (context, severity) => {
567
+ const findings = [];
568
+ visitSourceFile(context, (node) => {
569
+ const call = context.tsApi.isCallExpression(node) ? node : void 0;
570
+ const methodCall = getMethodCall(context.tsApi, node);
571
+ if (call === void 0 || methodCall?.methodName !== "map") return;
572
+ const receiverType = context.checker.getTypeAtLocation(methodCall.receiver);
573
+ if (!isResultLikeType(context.tsApi, context.checker, methodCall.receiver, receiverType)) return;
574
+ const [callback] = call.arguments;
575
+ const returnedResult = (callback === void 0 ? [] : getReturnedExpressions(context.tsApi, callback)).find((expression) => isResultLikeExpression(context, expression));
576
+ if (returnedResult === void 0) return;
577
+ const returnedType = context.checker.getTypeAtLocation(returnedResult);
578
+ const methodName = !isResultAsyncLikeType(context, methodCall.receiver, receiverType) && isResultAsyncLikeType(context, returnedResult, returnedType) ? "asyncAndThen" : "andThen";
579
+ findings.push(createFinding(context, methodCall.nameNode, "prefer-and-then", severity, `\`map\` creates a nested Result when its callback returns ${getTypeName(context, returnedResult, returnedType)}. Use \`${methodName}\` for fallible composition.`));
580
+ });
581
+ return findings;
582
+ };
583
+ const getTypedCatchMapperFindings = (context, severity) => {
584
+ const findings = [];
585
+ visitSourceFile(context, (node) => {
586
+ if (!context.tsApi.isCallExpression(node)) return;
587
+ const callName = getExpressionName(context.tsApi, node.expression);
588
+ if (callName === void 0 || !tryMapperCallNames.has(callName) || hasMapperArgument(context.tsApi, node)) return;
589
+ if (callName !== "fromThrowable" && callName !== "fromThrowableAsync") {
590
+ const errorTypes = getFirstResultErrorTypes(context, node, context.checker.getTypeAtLocation(node));
591
+ if (errorTypes.length > 0 && errorTypes.every((errorType) => !isUnknownOrAnyType(context.tsApi, errorType))) return;
592
+ }
593
+ findings.push(createFinding(context, node.expression, "typed-catch-mapper", severity, `\`${callName}\` without a catch mapper leaves the error channel as \`unknown\`. Map the caught value to a specific Resultar error.`));
594
+ });
595
+ return findings;
596
+ };
597
+ const collectResultarAwaitBoundaryBodies = (context) => {
598
+ const bodies = /* @__PURE__ */ new Set();
599
+ visitSourceFile(context, (node) => {
600
+ if (!context.tsApi.isCallExpression(node)) return;
601
+ const body = getResultarAwaitBoundaryBody(context.tsApi, node);
602
+ if (body !== void 0) bodies.add(body);
603
+ });
604
+ return bodies;
605
+ };
606
+ const collectResultarAwaitContextBodies = (context) => {
607
+ const bodies = /* @__PURE__ */ new Set();
608
+ visitSourceFile(context, (node) => {
609
+ if (!context.tsApi.isCallExpression(node)) return;
610
+ const body = getResultarAwaitContextBody(context.tsApi, node);
611
+ if (body !== void 0) bodies.add(body);
612
+ });
613
+ return bodies;
614
+ };
615
+ const getNoUnsafeAwaitFindings = (context, severity, mode) => {
616
+ const findings = [];
617
+ const boundaryBodies = collectResultarAwaitBoundaryBodies(context);
618
+ const contextBodies = collectResultarAwaitContextBodies(context);
619
+ const visit = (node, insideResultarBoundary, insideResultarContext) => {
620
+ const startsResultarBoundary = boundaryBodies.has(node);
621
+ const currentInsideBoundary = insideResultarBoundary || startsResultarBoundary;
622
+ const startsResultarContext = contextBodies.has(node) || isResultarAsyncFunctionContext(context, node);
623
+ const currentInsideContext = insideResultarContext || startsResultarContext;
624
+ if ((mode === "all" || currentInsideContext) && context.tsApi.isAwaitExpression(node) && !currentInsideBoundary && !isSafeAwaitExpression(context, node.expression)) findings.push(createFinding(context, node, "no-unsafe-await", severity, "Wrap this awaited Promise in tryAsync, tryResultAsync, tryCatchAsync, or fromThrowableAsync so rejections stay in the Resultar error channel."));
625
+ if (node !== context.sourceFile && isFunctionLike(context.tsApi, node)) {
626
+ context.tsApi.forEachChild(node, (child) => visit(child, startsResultarBoundary, startsResultarContext));
627
+ return;
628
+ }
629
+ context.tsApi.forEachChild(node, (child) => visit(child, currentInsideBoundary, currentInsideContext));
630
+ };
631
+ visit(context.sourceFile, false, false);
632
+ return findings;
633
+ };
634
+ const getNoTryCatchInSafeTryFindings = (context, severity) => {
635
+ const findings = [];
636
+ visitSourceFile(context, (node) => {
637
+ if (!context.tsApi.isCallExpression(node)) return;
638
+ const safeTryBody = getSafeTryBody(context.tsApi, node);
639
+ if (safeTryBody === void 0) return;
640
+ visitSafeTryBody(context.tsApi, safeTryBody, (bodyNode) => {
641
+ if (context.tsApi.isTryStatement(bodyNode)) findings.push(createFinding(context, bodyNode, "no-try-catch-in-safe-try", severity, "Avoid raw try/catch inside `safeTry`. Use `safeTry({ try, catch })`, `tryResult`, or `tryResultAsync` to keep failures typed."));
642
+ });
643
+ });
644
+ return findings;
645
+ };
646
+ const getYieldStarInSafeTryFindings = (context, severity) => {
647
+ const findings = [];
648
+ visitSourceFile(context, (node) => {
649
+ if (!context.tsApi.isCallExpression(node)) return;
650
+ const safeTryBody = getSafeTryBody(context.tsApi, node);
651
+ if (safeTryBody === void 0) return;
652
+ visitSafeTryBody(context.tsApi, safeTryBody, (bodyNode) => {
653
+ if (context.tsApi.isYieldExpression(bodyNode) && bodyNode.asteriskToken === void 0 && bodyNode.expression !== void 0 && isResultLikeExpression(context, bodyNode.expression)) findings.push(createFinding(context, bodyNode, "yield-star-in-safe-try", severity, "Use `yield*` when unwrapping Resultar values inside `safeTry`."));
654
+ });
655
+ });
656
+ return findings;
657
+ };
658
+ const getUnsafeResultTypeAssertionFindings = (context, severity) => {
659
+ const findings = [];
660
+ visitSourceFile(context, (node) => {
661
+ if (!context.tsApi.isAsExpression(node) && !context.tsApi.isTypeAssertionExpression(node)) return;
662
+ const { expression } = node;
663
+ const originalType = context.checker.getTypeAtLocation(expression);
664
+ const assertedType = context.checker.getTypeAtLocation(node);
665
+ const originalParts = getResultTypeParts(context, expression, originalType);
666
+ const assertedParts = getResultTypeParts(context, node, assertedType);
667
+ if (originalParts.length === 0 || assertedParts.length === 0) return;
668
+ const narrowedErrors = originalParts.flatMap((originalPart) => assertedParts.filter((assertedPart) => originalPart.error !== void 0 && assertedPart.error !== void 0 && !isUnknownOrAnyType(context.tsApi, originalPart.error) && !context.checker.isTypeAssignableTo(originalPart.error, assertedPart.error)).map((assertedPart) => ({
669
+ asserted: assertedPart.error,
670
+ original: originalPart.error
671
+ })));
672
+ if (narrowedErrors.length === 0) return;
673
+ const details = narrowedErrors.map(({ asserted, original }) => `\`${getTypeName(context, expression, original)}\` to \`${getTypeName(context, node, asserted)}\``).join(", ");
674
+ findings.push(createFinding(context, node, "unsafe-result-type-assertion", severity, `This assertion narrows the Resultar error channel unsafely (${details}). Prefer a real recovery or mapping step.`));
675
+ });
676
+ return findings;
677
+ };
678
+ const getPreferTaggedErrorFindings = (context, severity) => {
679
+ const findings = [];
680
+ visitSourceFile(context, (node) => {
681
+ if (context.tsApi.isClassDeclaration(node) && classExtendsNativeError(context.tsApi, node)) {
682
+ findings.push(createFinding(context, node.name ?? node, "prefer-tagged-error", severity, "Prefer `createTaggedError` for Resultar domain errors so failures keep a stable tag and typed metadata."));
683
+ return;
684
+ }
685
+ if (!context.tsApi.isCallExpression(node) || getExpressionName(context.tsApi, node.expression) !== "err") return;
686
+ const [errorArgument] = node.arguments;
687
+ if (errorArgument === void 0) return;
688
+ const unwrappedErrorArgument = unwrapExpression(context.tsApi, errorArgument);
689
+ if (context.tsApi.isNewExpression(unwrappedErrorArgument) && getExpressionName(context.tsApi, unwrappedErrorArgument.expression) === "Error") findings.push(createFinding(context, errorArgument, "prefer-tagged-error", severity, "Prefer a `createTaggedError` instance over `new Error(...)` in Resultar error channels."));
690
+ });
691
+ return findings;
692
+ };
693
+ const getTaggedErrorNameMatchFindings = (context, severity) => {
694
+ const findings = [];
695
+ visitSourceFile(context, (node) => {
696
+ if (!context.tsApi.isClassDeclaration(node) || node.name === void 0) return;
697
+ const options = getCreateTaggedErrorOptions(context.tsApi, node);
698
+ const nameInitializer = options === void 0 ? void 0 : getTaggedErrorName(context.tsApi, options);
699
+ const className = getIdentifierText(node.name);
700
+ if (nameInitializer === void 0 || nameInitializer.text === className) return;
701
+ findings.push(createFinding(context, nameInitializer, "tagged-error-name-match", severity, `Tagged error name \`${nameInitializer.text}\` should match class name \`${className}\`.`));
702
+ });
703
+ return findings;
704
+ };
705
+ const getNoTaggedErrorConstructorOverrideFindings = (context, severity) => {
706
+ const findings = [];
707
+ visitSourceFile(context, (node) => {
708
+ if (!context.tsApi.isClassDeclaration(node) || getCreateTaggedErrorOptions(context.tsApi, node) === void 0) return;
709
+ for (const member of node.members) if (context.tsApi.isConstructorDeclaration(member)) findings.push(createFinding(context, member, "no-tagged-error-constructor-override", severity, "Do not override the constructor generated by `createTaggedError`; it owns template props, cause, and serialization behavior."));
710
+ });
711
+ return findings;
712
+ };
713
+ const getNoUselessRecoveryFindings = (context, severity) => {
714
+ const findings = [];
715
+ visitSourceFile(context, (node) => {
716
+ const methodCall = getMethodCall(context.tsApi, node);
717
+ if (methodCall === void 0 || !recoveryMethods.has(methodCall.methodName)) return;
718
+ const receiverType = context.checker.getTypeAtLocation(methodCall.receiver);
719
+ const errorTypes = getFirstResultErrorTypes(context, methodCall.receiver, receiverType);
720
+ if (errorTypes.length === 0 || !errorTypes.every((errorType) => isNeverType(context.tsApi, errorType))) return;
721
+ findings.push(createFinding(context, methodCall.nameNode, "no-useless-recovery", severity, `\`${methodCall.methodName}\` cannot run because this Resultar value has \`never\` in the error channel.`));
722
+ });
723
+ return findings;
724
+ };
725
+ const getSourceFileResultarFindings = (tsApi, checker, sourceFile, options = {}) => {
726
+ const normalizedOptions = normalizeResultarRulesOptions(options);
727
+ const context = {
728
+ checker,
729
+ sourceFile,
730
+ tsApi
731
+ };
732
+ const findings = [];
733
+ const noDiscardSeverity = getRuleSeverity(normalizedOptions, "no-discard");
734
+ if (noDiscardSeverity !== void 0) findings.push(...getSourceFileNoDiscardFindings(tsApi, checker, sourceFile, { mode: normalizedOptions.noDiscardMode }).map((finding) => ({
735
+ ...finding,
736
+ severity: noDiscardSeverity
737
+ })));
738
+ const noUnsafeAwaitSeverity = getRuleSeverity(normalizedOptions, "no-unsafe-await");
739
+ if (noUnsafeAwaitSeverity !== void 0) findings.push(...getNoUnsafeAwaitFindings(context, noUnsafeAwaitSeverity, normalizedOptions.noUnsafeAwaitMode));
740
+ const ruleFns = [
741
+ ["prefer-map-err", getPreferMapErrFindings],
742
+ ["prefer-and-then", getPreferAndThenFindings],
743
+ ["typed-catch-mapper", getTypedCatchMapperFindings],
744
+ ["no-try-catch-in-safe-try", getNoTryCatchInSafeTryFindings],
745
+ ["yield-star-in-safe-try", getYieldStarInSafeTryFindings],
746
+ ["unsafe-result-type-assertion", getUnsafeResultTypeAssertionFindings],
747
+ ["prefer-tagged-error", getPreferTaggedErrorFindings],
748
+ ["tagged-error-name-match", getTaggedErrorNameMatchFindings],
749
+ ["no-tagged-error-constructor-override", getNoTaggedErrorConstructorOverrideFindings],
750
+ ["no-useless-recovery", getNoUselessRecoveryFindings]
751
+ ];
752
+ for (const [ruleName, ruleFn] of ruleFns) {
753
+ const severity = getRuleSeverity(normalizedOptions, ruleName);
754
+ if (severity !== void 0) findings.push(...ruleFn(context, severity));
755
+ }
756
+ return findings.toSorted((left, right) => left.start - right.start || left.rule.localeCompare(right.rule));
757
+ };
758
+ const getProgramResultarFindings = (tsApi, program, options = {}) => {
759
+ const checker = program.getTypeChecker();
760
+ const findings = [];
761
+ for (const sourceFile of program.getSourceFiles()) if (!sourceFile.isDeclarationFile && !sourceFile.fileName.includes("/node_modules/") && !sourceFile.fileName.includes("\\node_modules\\")) findings.push(...getSourceFileResultarFindings(tsApi, checker, sourceFile, options));
762
+ return findings;
763
+ };
764
+ //#endregion
765
+ //#region src/plugin-options.ts
766
+ const isRecord$1 = (value) => typeof value === "object" && value !== null;
767
+ const isResultarPluginName = (value) => value === "resultar-check" || value === "resultar-lint";
768
+ const parsePluginOptions = (config) => {
769
+ if (!isRecord$1(config)) return defaultResultarRulesOptions;
770
+ return {
771
+ noDiscard: normalizeRuleSeverity(config.noDiscard, defaultResultarRulesOptions.noDiscard),
772
+ noDiscardMode: normalizeNoDiscardMode(config.noDiscardMode),
773
+ noTaggedErrorConstructorOverride: normalizeRuleSeverity(config.noTaggedErrorConstructorOverride, defaultResultarRulesOptions.noTaggedErrorConstructorOverride),
774
+ noTryCatchInSafeTry: normalizeRuleSeverity(config.noTryCatchInSafeTry, defaultResultarRulesOptions.noTryCatchInSafeTry),
775
+ noUnsafeAwait: normalizeRuleSeverity(config.noUnsafeAwait, defaultResultarRulesOptions.noUnsafeAwait),
776
+ noUnsafeAwaitMode: normalizeNoUnsafeAwaitMode(config.noUnsafeAwaitMode),
777
+ noUselessRecovery: normalizeRuleSeverity(config.noUselessRecovery, defaultResultarRulesOptions.noUselessRecovery),
778
+ preferAndThen: normalizeRuleSeverity(config.preferAndThen, defaultResultarRulesOptions.preferAndThen),
779
+ preferMapErr: normalizeRuleSeverity(config.preferMapErr, defaultResultarRulesOptions.preferMapErr),
780
+ preferTaggedError: normalizeRuleSeverity(config.preferTaggedError, defaultResultarRulesOptions.preferTaggedError),
781
+ taggedErrorNameMatch: normalizeRuleSeverity(config.taggedErrorNameMatch, defaultResultarRulesOptions.taggedErrorNameMatch),
782
+ typedCatchMapper: normalizeRuleSeverity(config.typedCatchMapper, defaultResultarRulesOptions.typedCatchMapper),
783
+ unsafeResultTypeAssertion: normalizeRuleSeverity(config.unsafeResultTypeAssertion, defaultResultarRulesOptions.unsafeResultTypeAssertion),
784
+ yieldStarInSafeTry: normalizeRuleSeverity(config.yieldStarInSafeTry, defaultResultarRulesOptions.yieldStarInSafeTry)
785
+ };
786
+ };
787
+ const findResultarPluginConfig = (plugins) => {
788
+ const plugin = plugins?.find((entry) => isRecord$1(entry) && isResultarPluginName(entry.name));
789
+ return plugin === void 0 ? void 0 : parsePluginOptions(plugin);
790
+ };
791
+ //#endregion
792
+ //#region src/lint.ts
793
+ const usage = `Usage: resultar-check -p tsconfig.json --noEmit
794
+
795
+ Flags:
796
+ --mode <direct|must-use> Check mode. Defaults to tsconfig plugin noDiscardMode or must-use.
797
+ -p, --project <path> TypeScript project file to inspect. Defaults to tsconfig.json.
798
+ -h, --help Show this help message.
799
+
800
+ Runs TypeScript 7 first, then all enabled Resultar rules from tsconfig plugin options.
801
+ `;
802
+ const failure = (error) => ({
803
+ error,
804
+ ok: false
805
+ });
806
+ const success = (findings) => ({
807
+ findings,
808
+ ok: true
809
+ });
810
+ const cliError = (message) => failure(new Error(message));
811
+ const requireFromPackage = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href);
812
+ const isTypeScript7Version = (version) => version.startsWith("7.");
813
+ const resolvePackageFromRoot = (rootDir, specifier) => {
814
+ const requireFromRoot = (0, node_module.createRequire)((0, node_path.resolve)(rootDir, "package.json"));
815
+ try {
816
+ return requireFromRoot.resolve(specifier);
817
+ } catch {
818
+ return requireFromPackage.resolve(specifier);
819
+ }
820
+ };
821
+ const readPackageVersion = (packageJson) => {
822
+ const parsed = JSON.parse((0, node_fs.readFileSync)(packageJson, "utf8"));
823
+ if (!isRecord(parsed) || typeof parsed.version !== "string") throw new TypeError(`Unable to read package version from ${packageJson}`);
824
+ return parsed.version;
825
+ };
826
+ const resolveOptionalPackage = (rootDir, specifier) => {
827
+ try {
828
+ return resolvePackageFromRoot(rootDir, specifier);
829
+ } catch {
830
+ return;
831
+ }
832
+ };
833
+ const resolveTypeScript7PackageJson = (rootDir) => {
834
+ for (const candidate of ["typescript/package.json", "typescript-7/package.json"]) {
835
+ const packageJson = resolveOptionalPackage(rootDir, candidate);
836
+ if (packageJson !== void 0 && isTypeScript7Version(readPackageVersion(packageJson))) return {
837
+ ok: true,
838
+ packageJson
839
+ };
840
+ }
841
+ return failure(/* @__PURE__ */ new Error("Unable to resolve TypeScript 7. Install typescript@rc or typescript-7@npm:typescript@rc in this project."));
842
+ };
843
+ const parseCheckArgs = (args) => {
844
+ let mode = void 0;
845
+ let project = void 0;
846
+ let help = false;
847
+ const tscArgs = [];
848
+ for (let index = 0; index < args.length; index += 1) {
849
+ const arg = args[index];
850
+ if (arg === void 0 || arg === "") continue;
851
+ if (arg === "--help" || arg === "-h") {
852
+ help = true;
853
+ continue;
854
+ }
855
+ if (arg === "--mode") {
856
+ const nextArg = args[index + 1];
857
+ if (nextArg === void 0 || nextArg === "") return cliError("--mode requires direct or must-use");
858
+ if (nextArg !== "direct" && nextArg !== "must-use") return cliError(`Unknown --mode value: ${nextArg}`);
859
+ mode = nextArg;
860
+ index += 1;
861
+ continue;
862
+ }
863
+ if (arg.startsWith("--mode=")) {
864
+ const nextMode = arg.slice(7);
865
+ if (nextMode !== "direct" && nextMode !== "must-use") return cliError(`Unknown --mode value: ${nextMode}`);
866
+ mode = nextMode;
867
+ continue;
868
+ }
869
+ if (arg === "--project" || arg === "-p") {
870
+ const nextArg = args[index + 1];
871
+ if (nextArg === void 0 || nextArg === "") return cliError(`${arg} requires a path`);
872
+ project = nextArg;
873
+ tscArgs.push(arg, nextArg);
874
+ index += 1;
875
+ continue;
876
+ }
877
+ if (arg.startsWith("--project=")) project = arg.slice(10);
878
+ tscArgs.push(arg);
879
+ }
880
+ if (project === "") return cliError("--project requires a path");
881
+ if (project === void 0) return {
882
+ ok: true,
883
+ options: mode === void 0 ? {
884
+ help,
885
+ tscArgs
886
+ } : {
887
+ help,
888
+ mode,
889
+ tscArgs
890
+ }
891
+ };
892
+ return {
893
+ ok: true,
894
+ options: mode === void 0 ? {
895
+ help,
896
+ project,
897
+ tscArgs
898
+ } : {
899
+ help,
900
+ mode,
901
+ project,
902
+ tscArgs
903
+ }
904
+ };
905
+ };
906
+ const readProject = (tsApi, projectPath) => {
907
+ const formatHost = {
908
+ getCanonicalFileName: (fileName) => fileName,
909
+ getCurrentDirectory: () => process.cwd(),
910
+ getNewLine: () => "\n"
911
+ };
912
+ const config = tsApi.readConfigFile(projectPath, (fileName) => tsApi.sys.readFile(fileName));
913
+ if (config.error) return failure(new Error(tsApi.formatDiagnosticsWithColorAndContext([config.error], formatHost)));
914
+ const parsed = tsApi.parseJsonConfigFileContent(config.config, tsApi.sys, (0, node_path.resolve)(projectPath, ".."), void 0, projectPath);
915
+ if (parsed.errors.length > 0) return failure(new Error(tsApi.formatDiagnosticsWithColorAndContext(parsed.errors, formatHost)));
916
+ return {
917
+ config: config.config,
918
+ ok: true,
919
+ parsed
920
+ };
921
+ };
922
+ const isRecord = (value) => typeof value === "object" && value !== null;
923
+ const getProjectRuleOptions = (config) => {
924
+ if (!isRecord(config) || !isRecord(config.compilerOptions)) return;
925
+ const { plugins } = config.compilerOptions;
926
+ if (!Array.isArray(plugins)) return;
927
+ return findResultarPluginConfig(plugins);
928
+ };
929
+ const resolveTypeScriptApi = (_rootDir) => {
930
+ try {
931
+ return {
932
+ ok: true,
933
+ tsApi: requireFromPackage("typescript")
934
+ };
935
+ } catch {
936
+ return failure(/* @__PURE__ */ new Error("Unable to resolve the internal TypeScript diagnostics API bundled with resultar-check."));
937
+ }
938
+ };
939
+ const findResultarLintFindings = (options = {}) => {
940
+ const rootDir = (0, node_path.resolve)(options.rootDir ?? process.cwd());
941
+ const projectPath = (0, node_path.resolve)(rootDir, options.project ?? "tsconfig.json");
942
+ const resolvedTypeScript = resolveTypeScriptApi(rootDir);
943
+ if (!resolvedTypeScript.ok) return resolvedTypeScript;
944
+ const { tsApi } = resolvedTypeScript;
945
+ const project = readProject(tsApi, projectPath);
946
+ if (!project.ok) return project;
947
+ const projectRuleOptions = getProjectRuleOptions(project.config);
948
+ return success(getProgramResultarFindings(tsApi, tsApi.createProgram(project.parsed.fileNames, project.parsed.options), {
949
+ ...projectRuleOptions,
950
+ ...options.rules,
951
+ noDiscardMode: normalizeNoDiscardMode(options.mode ?? options.rules?.noDiscardMode ?? projectRuleOptions?.noDiscardMode)
952
+ }));
953
+ };
954
+ const formatFinding = (finding, rootDir) => {
955
+ return [`${(0, node_path.relative)(rootDir, finding.file)}:${finding.line}:${finding.column} resultar/${finding.rule}`, finding.message].join(" - ");
956
+ };
957
+ const passthroughArgs = new Set(["--version", "-v"]);
958
+ const shouldSkipResultarDiagnostics = (args) => args.some((arg) => passthroughArgs.has(arg));
959
+ const runNode = (script, args) => {
960
+ const result = (0, node_child_process.spawnSync)(process.execPath, [script, ...args], { stdio: "inherit" });
961
+ if (result.error !== void 0) throw result.error;
962
+ return result.status ?? 1;
963
+ };
964
+ const runResultarCheckCli = (args = process.argv.slice(2)) => {
965
+ const rootDir = process.cwd();
966
+ const parsedArgs = parseCheckArgs(args);
967
+ if (!parsedArgs.ok) {
968
+ process.stderr.write(`${parsedArgs.error.message}\n`);
969
+ return 1;
970
+ }
971
+ if (parsedArgs.options.help) {
972
+ process.stdout.write(usage);
973
+ return 0;
974
+ }
975
+ const resolvedTypeScript = resolveTypeScript7PackageJson(rootDir);
976
+ if (!resolvedTypeScript.ok) {
977
+ process.stderr.write(`${resolvedTypeScript.error.message}\n`);
978
+ return 1;
979
+ }
980
+ const tscStatus = runNode((0, node_path.join)((0, node_path.dirname)(resolvedTypeScript.packageJson), "bin/tsc"), [...parsedArgs.options.tscArgs]);
981
+ if (tscStatus !== 0 || shouldSkipResultarDiagnostics(args)) return tscStatus;
982
+ const result = parsedArgs.options.project === void 0 ? findResultarLintFindings({
983
+ mode: parsedArgs.options.mode,
984
+ rootDir
985
+ }) : findResultarLintFindings({
986
+ mode: parsedArgs.options.mode,
987
+ project: parsedArgs.options.project,
988
+ rootDir
989
+ });
990
+ if (!result.ok) {
991
+ process.stderr.write(`${result.error.message}\n`);
992
+ return 1;
993
+ }
994
+ if (result.findings.length === 0) return 0;
995
+ process.stderr.write(`${result.findings.map((finding) => formatFinding(finding, rootDir)).join("\n")}\n`);
996
+ return 1;
997
+ };
998
+ //#endregion
999
+ Object.defineProperty(exports, "findResultarLintFindings", {
1000
+ enumerable: true,
1001
+ get: function() {
1002
+ return findResultarLintFindings;
1003
+ }
1004
+ });
1005
+ Object.defineProperty(exports, "getSourceFileResultarFindings", {
1006
+ enumerable: true,
1007
+ get: function() {
1008
+ return getSourceFileResultarFindings;
1009
+ }
1010
+ });
1011
+ Object.defineProperty(exports, "normalizeResultarRulesOptions", {
1012
+ enumerable: true,
1013
+ get: function() {
1014
+ return normalizeResultarRulesOptions;
1015
+ }
1016
+ });
1017
+ Object.defineProperty(exports, "parsePluginOptions", {
1018
+ enumerable: true,
1019
+ get: function() {
1020
+ return parsePluginOptions;
1021
+ }
1022
+ });
1023
+ Object.defineProperty(exports, "runResultarCheckCli", {
1024
+ enumerable: true,
1025
+ get: function() {
1026
+ return runResultarCheckCli;
1027
+ }
1028
+ });
1029
+
1030
+ //# sourceMappingURL=lint-D-D2dmLi.cjs.map