unguard 0.7.0 → 0.8.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 @@
1
+ {"version":3,"sources":["../src/rules/ts/no-empty-catch.ts","../src/rules/ts/no-non-null-assertion.ts","../src/rules/ts/no-double-negation-coercion.ts","../src/typecheck/utils.ts","../src/rules/ts/no-ts-ignore.ts","../src/rules/ts/no-nullish-coalescing.ts","../src/rules/ts/no-optional-call.ts","../src/rules/ts/no-optional-property-access.ts","../src/rules/ts/no-optional-element-access.ts","../src/rules/ts/no-logical-or-fallback.ts","../src/rules/ts/no-null-ternary-normalization.ts","../src/rules/ts/no-any-cast.ts","../src/rules/ts/no-explicit-any-annotation.ts","../src/rules/ts/no-inline-type-in-params.ts","../src/rules/ts/no-type-assertion.ts","../src/rules/ts/no-redundant-existence-guard.ts","../src/rules/ts/prefer-default-param-value.ts","../src/rules/ts/prefer-required-param-with-guard.ts","../src/rules/cross-file/duplicate-type-declaration.ts","../src/rules/cross-file/duplicate-function-declaration.ts","../src/rules/cross-file/optional-arg-always-used.ts","../src/rules/ts/no-catch-return.ts","../src/rules/ts/no-error-rewrap.ts","../src/rules/cross-file/explicit-null-arg.ts","../src/rules/cross-file/duplicate-function-name.ts","../src/rules/cross-file/duplicate-type-name.ts","../src/rules/ts/no-dynamic-import.ts","../src/rules/index.ts","../src/engine.ts","../src/rules/types.ts","../src/collect/index.ts","../src/utils/hash.ts","../src/collect/type-registry.ts","../src/collect/function-registry.ts","../src/typecheck/program.ts","../src/typecheck/walk.ts"],"sourcesContent":["import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noEmptyCatch: TSRule = {\n kind: \"ts\",\n id: \"no-empty-catch\",\n severity: \"error\",\n message: \"Empty catch blocks hide failures; handle, annotate, or rethrow explicitly\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isCatchClause(node)) return;\n const block = node.block;\n if (block.statements.length > 0) return;\n\n // Check for comments inside the block braces\n const blockStart = block.getStart(ctx.sourceFile);\n const blockEnd = block.getEnd();\n const inner = ctx.source.slice(blockStart + 1, blockEnd - 1);\n if (inner.includes(\"//\") || inner.includes(\"/*\")) return;\n\n ctx.report(node);\n },\n};\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noNonNullAssertion: TSRule = {\n kind: \"ts\",\n id: \"no-non-null-assertion\",\n severity: \"warning\",\n message: \"Non-null assertion (!) overrides the type checker; narrow with a type guard or fix the type so it's not nullable\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isNonNullExpression(node)) return;\n\n const inner = node.expression;\n\n // Type already non-nullable -> ! is redundant noise, not worth flagging\n if (!ctx.isNullable(inner)) return;\n\n // External library type gap -> suppress\n if (ctx.isExternal(inner)) return;\n\n // split(...)[n]! — always safe\n if (isSplitElementAccess(inner)) return;\n\n // filter(...)[n]! — safe pattern (narrowed array)\n if (isFilterElementAccess(inner)) return;\n\n // arr[n]! after a length guard — provably safe\n if (isLengthGuardedAccess(inner)) return;\n\n ctx.report(node);\n },\n};\n\nfunction isSplitElementAccess(node: ts.Node): boolean {\n if (!ts.isElementAccessExpression(node)) return false;\n const obj = node.expression;\n if (!ts.isCallExpression(obj)) return false;\n const callee = obj.expression;\n if (!ts.isPropertyAccessExpression(callee)) return false;\n return callee.name.text === \"split\";\n}\n\nfunction isFilterElementAccess(node: ts.Node): boolean {\n // Direct: items.filter(...)[n]!\n if (ts.isElementAccessExpression(node)) {\n const obj = node.expression;\n if (ts.isCallExpression(obj)) {\n const callee = obj.expression;\n if (ts.isPropertyAccessExpression(callee) && callee.name.text === \"filter\") return true;\n }\n // Indirect: const filtered = items.filter(...); filtered[n]!\n if (ts.isIdentifier(obj)) {\n const init = findVariableInit(obj);\n if (init && ts.isCallExpression(init)) {\n const callee = init.expression;\n if (ts.isPropertyAccessExpression(callee) && callee.name.text === \"filter\") return true;\n }\n }\n }\n return false;\n}\n\n/** Detect arr[n]! where arr.length is checked in a preceding guard or enclosing for-loop. */\nfunction isLengthGuardedAccess(node: ts.Node): boolean {\n if (!ts.isElementAccessExpression(node)) return false;\n const arr = node.expression;\n\n const arrName = getIdentifierName(arr);\n if (!arrName) return false;\n\n // Check for enclosing for-loop: for (let i = 0; i < arr.length; i++)\n if (isInsideForLoopBoundedBy(node, arrName)) return true;\n\n // Check for preceding length guard in same block\n return hasPrecedingLengthGuard(node, arrName);\n}\n\n/** Walk ancestors to find a for-loop whose condition bounds against arrName.length. */\nfunction isInsideForLoopBoundedBy(node: ts.Node, arrName: string): boolean {\n let current: ts.Node = node;\n while (current.parent) {\n current = current.parent;\n if (ts.isForStatement(current) && current.condition) {\n if (isLengthBoundCondition(current.condition, arrName)) return true;\n }\n }\n return false;\n}\n\n/** Check if a condition is `i < arr.length` or similar. */\nfunction isLengthBoundCondition(cond: ts.Expression, arrName: string): boolean {\n if (!ts.isBinaryExpression(cond)) return false;\n const op = cond.operatorToken.kind;\n // i < arr.length or i <= arr.length - 1\n if (op === ts.SyntaxKind.LessThanToken || op === ts.SyntaxKind.LessThanEqualsToken) {\n return isLengthAccess(cond.right, arrName);\n }\n // arr.length > i\n if (op === ts.SyntaxKind.GreaterThanToken || op === ts.SyntaxKind.GreaterThanEqualsToken) {\n return isLengthAccess(cond.left, arrName);\n }\n return false;\n}\n\n/** Check if node is `arrName.length`. */\nfunction isLengthAccess(node: ts.Node, arrName: string): boolean {\n if (!ts.isPropertyAccessExpression(node)) return false;\n if (node.name.text !== \"length\") return false;\n return getIdentifierName(node.expression) === arrName;\n}\n\n/**\n * Check if there's a preceding statement in the same block (or a parent block)\n * that guards on arrName.length, implying the array is non-empty.\n *\n * Patterns:\n * - if (arr.length === 0) return;\n * - if (arr.length < N) return; (where N >= 1)\n * - if (arr.length > 0) { ... arr[0]! ... }\n * - if (arr.length !== 0) { ... arr[0]! ... }\n * - if (arr.length >= 1) { ... arr[0]! ... }\n * - if (arr.length !== N) return; (where N >= 1)\n */\nfunction hasPrecedingLengthGuard(node: ts.Node, arrName: string): boolean {\n // Walk up to find the containing block/if-statement\n let current: ts.Node = node;\n while (current.parent) {\n const parent = current.parent;\n\n // Case 1: we're in a block and a preceding if-statement guards length with early return\n if (ts.isBlock(parent)) {\n for (const stmt of parent.statements) {\n if (stmt === current || stmt.pos >= current.pos) break;\n if (ts.isIfStatement(stmt) && isLengthGuardWithEarlyExit(stmt, arrName)) return true;\n }\n }\n\n // Case 2: we're inside the then-branch of an if that checks length > 0\n if (ts.isIfStatement(parent) && parent.thenStatement === current) {\n if (isPositiveLengthCheck(parent.expression, arrName)) return true;\n }\n // Also handle: if (...) { <block containing current> }\n if (ts.isBlock(current) && ts.isIfStatement(parent) && parent.thenStatement === current) {\n if (isPositiveLengthCheck(parent.expression, arrName)) return true;\n }\n\n current = parent;\n }\n return false;\n}\n\n/** if (arr.length === 0) return/throw; or if (arr.length < 1) return/throw; etc. */\nfunction isLengthGuardWithEarlyExit(stmt: ts.IfStatement, arrName: string): boolean {\n if (!isEarlyExit(stmt.thenStatement)) return false;\n return isZeroLengthCheck(stmt.expression, arrName);\n}\n\n/** Checks if condition means \"arr is empty\": arr.length === 0, arr.length < 1, arr.length !== N (N>=1) */\nfunction isZeroLengthCheck(expr: ts.Expression, arrName: string): boolean {\n if (!ts.isBinaryExpression(expr)) return false;\n const op = expr.operatorToken.kind;\n\n // arr.length === 0\n if ((op === ts.SyntaxKind.EqualsEqualsEqualsToken || op === ts.SyntaxKind.EqualsEqualsToken)) {\n if (isLengthAccess(expr.left, arrName) && isNumericLiteralValue(expr.right, 0)) return true;\n if (isLengthAccess(expr.right, arrName) && isNumericLiteralValue(expr.left, 0)) return true;\n }\n\n // arr.length < 1 (or any N >= 1)\n if (op === ts.SyntaxKind.LessThanToken) {\n if (isLengthAccess(expr.left, arrName) && isNumericLiteralGte(expr.right, 1)) return true;\n }\n\n // arr.length !== N where N >= 1 (e.g., if (arr.length !== 1) return; arr[0]!)\n if ((op === ts.SyntaxKind.ExclamationEqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsToken)) {\n if (isLengthAccess(expr.left, arrName) && isNumericLiteralGte(expr.right, 1)) return true;\n }\n\n return false;\n}\n\n/** Checks if condition means \"arr is non-empty\": arr.length > 0, arr.length !== 0, arr.length >= 1 */\nfunction isPositiveLengthCheck(expr: ts.Expression, arrName: string): boolean {\n if (!ts.isBinaryExpression(expr)) return false;\n const op = expr.operatorToken.kind;\n\n // arr.length > 0\n if (op === ts.SyntaxKind.GreaterThanToken) {\n if (isLengthAccess(expr.left, arrName) && isNumericLiteralValue(expr.right, 0)) return true;\n }\n\n // arr.length >= 1\n if (op === ts.SyntaxKind.GreaterThanEqualsToken) {\n if (isLengthAccess(expr.left, arrName) && isNumericLiteralGte(expr.right, 1)) return true;\n }\n\n // arr.length !== 0\n if ((op === ts.SyntaxKind.ExclamationEqualsEqualsToken || op === ts.SyntaxKind.ExclamationEqualsToken)) {\n if (isLengthAccess(expr.left, arrName) && isNumericLiteralValue(expr.right, 0)) return true;\n }\n\n return false;\n}\n\nfunction isEarlyExit(stmt: ts.Statement): boolean {\n if (ts.isReturnStatement(stmt) || ts.isThrowStatement(stmt)) return true;\n if (ts.isBlock(stmt) && stmt.statements.length === 1) {\n const inner = stmt.statements[0];\n if (inner === undefined) return false;\n return ts.isReturnStatement(inner) || ts.isThrowStatement(inner);\n }\n return false;\n}\n\nfunction isNumericLiteralValue(node: ts.Node, value: number): boolean {\n return ts.isNumericLiteral(node) && node.text === String(value);\n}\n\nfunction isNumericLiteralGte(node: ts.Node, min: number): boolean {\n return ts.isNumericLiteral(node) && Number(node.text) >= min;\n}\n\nfunction getIdentifierName(node: ts.Node): string | null {\n if (ts.isIdentifier(node)) return node.text;\n return null;\n}\n\nfunction findVariableInit(id: ts.Identifier): ts.Expression | undefined {\n const sourceFile = id.getSourceFile();\n let result: ts.Expression | undefined;\n function visit(node: ts.Node): void {\n if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === id.text && node.initializer) {\n result = node.initializer;\n }\n if (!result) ts.forEachChild(node, visit);\n }\n visit(sourceFile);\n return result;\n}\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\nimport { includesBooleanType } from \"../../typecheck/utils.ts\";\n\nexport const noDoubleNegationCoercion: TSRule = {\n kind: \"ts\",\n id: \"no-double-negation-coercion\",\n severity: \"info\",\n message: \"!! coercion hides intent; use an explicit check (!== null, !== undefined, .length > 0) so the condition documents what it tests\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isPrefixUnaryExpression(node)) return;\n if (node.operator !== ts.SyntaxKind.ExclamationToken) return;\n const inner = node.operand;\n if (!ts.isPrefixUnaryExpression(inner)) return;\n if (inner.operator !== ts.SyntaxKind.ExclamationToken) return;\n\n const operand = inner.operand;\n\n // Already boolean -> !! is a no-op, promote severity\n const innerType = ctx.checker.getTypeAtLocation(operand);\n if (includesBooleanType(innerType) && !(innerType.flags & ts.TypeFlags.Union)) {\n ctx.report(node, \"!! on an already-boolean type is a no-op; remove the double negation\");\n return;\n }\n\n // Bitwise expression: !!(flags & MASK) is the standard idiom for flag testing\n if (isBitwiseExpression(operand)) return;\n\n ctx.report(node);\n },\n};\n\nconst BITWISE_OPS = new Set([\n ts.SyntaxKind.AmpersandToken,\n ts.SyntaxKind.BarToken,\n ts.SyntaxKind.CaretToken,\n ts.SyntaxKind.LessThanLessThanToken,\n ts.SyntaxKind.GreaterThanGreaterThanToken,\n ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken,\n]);\n\nfunction isBitwiseExpression(node: ts.Node): boolean {\n // Direct: !!(a & b)\n if (ts.isBinaryExpression(node) && BITWISE_OPS.has(node.operatorToken.kind)) return true;\n // Parenthesized: !!((a & b) | c)\n if (ts.isParenthesizedExpression(node)) return isBitwiseExpression(node.expression);\n return false;\n}\n","import * as ts from \"typescript\";\n\nfunction hasFlags(flags: number, mask: number): boolean {\n return (flags & mask) !== 0;\n}\n\nconst NULLISH_FLAGS = ts.TypeFlags.Null | ts.TypeFlags.Undefined | ts.TypeFlags.Void;\n\nexport function isNullableType(checker: ts.TypeChecker, type: ts.Type): boolean {\n if (type.isUnion()) {\n return type.types.some((t) => hasFlags(t.flags, NULLISH_FLAGS));\n }\n return hasFlags(type.flags, NULLISH_FLAGS);\n}\n\nexport function isFromNodeModules(node: ts.Node): boolean {\n const sourceFile = node.getSourceFile();\n return sourceFile.fileName.includes(\"/node_modules/\");\n}\n\nexport function includesNumberType(type: ts.Type): boolean {\n if (type.isUnion()) {\n return type.types.some((t) => hasFlags(t.flags, ts.TypeFlags.NumberLike));\n }\n return hasFlags(type.flags, ts.TypeFlags.NumberLike);\n}\n\nexport function includesBooleanType(type: ts.Type): boolean {\n if (type.isUnion()) {\n return type.types.some((t) => hasFlags(t.flags, ts.TypeFlags.BooleanLike));\n }\n return hasFlags(type.flags, ts.TypeFlags.BooleanLike);\n}\n\nexport function isNullishLiteral(node: ts.Node): boolean {\n if (node.kind === ts.SyntaxKind.NullKeyword) return true;\n if (ts.isIdentifier(node) && node.text === \"undefined\") return true;\n return false;\n}\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noTsIgnore: TSRule = {\n kind: \"ts\",\n id: \"no-ts-ignore\",\n severity: \"error\",\n message: \"@ts-ignore / @ts-expect-error suppresses type checking; fix the underlying type issue\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n const ranges = ts.getLeadingCommentRanges(ctx.source, node.getFullStart());\n if (!ranges) return;\n for (const range of ranges) {\n const text = ctx.source.slice(range.pos, range.end);\n if (text.includes(\"@ts-ignore\") || text.includes(\"@ts-expect-error\")) {\n ctx.reportAtOffset(range.pos);\n }\n }\n },\n};\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noNullishCoalescing: TSRule = {\n kind: \"ts\",\n id: \"no-nullish-coalescing\",\n severity: \"warning\",\n message: \"Nullish coalescing (??) on a non-nullable type is unreachable; remove the fallback or fix the type upstream\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isBinaryExpression(node)) return;\n if (node.operatorToken.kind !== ts.SyntaxKind.QuestionQuestionToken) return;\n if (ctx.isNullable(node.left)) return;\n ctx.report(node);\n },\n};\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noOptionalCall: TSRule = {\n kind: \"ts\",\n id: \"no-optional-call\",\n severity: \"warning\",\n message: \"Optional call (?.) on a non-nullable function is redundant; call directly or fix the type upstream\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isCallExpression(node)) return;\n if (!node.questionDotToken) return;\n if (ctx.isNullable(node.expression)) return;\n ctx.report(node);\n },\n};\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noOptionalPropertyAccess: TSRule = {\n kind: \"ts\",\n id: \"no-optional-property-access\",\n severity: \"warning\",\n message: \"Optional chaining (?.) on a non-nullable type is redundant; use direct access or fix the type upstream\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isPropertyAccessExpression(node)) return;\n if (!node.questionDotToken) return;\n if (ctx.isNullable(node.expression)) return;\n ctx.report(node);\n },\n};\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noOptionalElementAccess: TSRule = {\n kind: \"ts\",\n id: \"no-optional-element-access\",\n severity: \"warning\",\n message: \"Optional element access (?.[]) on a non-nullable type is redundant; use direct access or fix the type upstream\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isElementAccessExpression(node)) return;\n if (!node.questionDotToken) return;\n if (ctx.isNullable(node.expression)) return;\n ctx.report(node);\n },\n};\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\nimport { includesNumberType, isNullableType } from \"../../typecheck/utils.ts\";\n\nexport const noLogicalOrFallback: TSRule = {\n kind: \"ts\",\n id: \"no-logical-or-fallback\",\n severity: \"warning\",\n message:\n '|| fallback on a data-structure lookup swallows valid falsy values (0, \"\"); use ?? to only catch null/undefined',\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isBinaryExpression(node)) return;\n if (node.operatorToken.kind !== ts.SyntaxKind.BarBarToken) return;\n\n const right = node.right;\n if (!isLiteral(right)) return;\n\n const left = node.left;\n const lhsType = ctx.checker.getTypeAtLocation(left);\n\n // Number() / parseInt() — || catches NaN/0 intentionally, ?? would not help\n if (isNumericCoercionCall(left)) return;\n\n // String type without undefined: || catches \"\" intentionally — suppress\n // String | undefined: || swallows \"\" AND catches undefined — should use ??\n if (isStringNotNullable(lhsType, ctx.checker)) return;\n\n // LHS includes number and RHS is not 0 -> catches bugs like `seed || undefined`\n if (includesNumberType(lhsType) && !isZeroLiteral(right)) {\n ctx.report(node, '|| on a numeric type swallows 0; use ?? to only catch null/undefined');\n return;\n }\n\n // Data-structure lookups: .get(), .find(), x[key], optional chaining\n if (isDataStructureLookup(left)) {\n ctx.report(node);\n }\n },\n};\n\n/** LHS is string (or string-like) without null/undefined in the union. */\nfunction isStringNotNullable(type: ts.Type, checker: ts.TypeChecker): boolean {\n if (isNullableType(checker, type)) return false;\n if (type.isUnion()) {\n return type.types.every((t) => (t.flags & ts.TypeFlags.StringLike) !== 0);\n }\n return (type.flags & ts.TypeFlags.StringLike) !== 0;\n}\n\nfunction isLiteral(node: ts.Node): boolean {\n if (ts.isStringLiteral(node) || ts.isNumericLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return true;\n if (ts.isTemplateExpression(node)) return true;\n if (ts.isArrayLiteralExpression(node) || ts.isObjectLiteralExpression(node)) return true;\n if (ts.isIdentifier(node) && node.text === \"undefined\") return true;\n if (node.kind === ts.SyntaxKind.NullKeyword) return true;\n if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) return true;\n return false;\n}\n\n/** Number(...) or parseInt(...) — returns number, || catches NaN/0, ?? wouldn't help */\nfunction isNumericCoercionCall(node: ts.Node): boolean {\n if (!ts.isCallExpression(node)) return false;\n if (!ts.isIdentifier(node.expression)) return false;\n const name = node.expression.text;\n return name === \"Number\" || name === \"parseInt\" || name === \"parseFloat\";\n}\n\nfunction isZeroLiteral(node: ts.Node): boolean {\n return ts.isNumericLiteral(node) && node.text === \"0\";\n}\n\nfunction isDataStructureLookup(left: ts.Node): boolean {\n // Flag: LHS is .get(), .find(), .getStore() call\n if (ts.isCallExpression(left)) {\n const callee = left.expression;\n if (ts.isPropertyAccessExpression(callee)) {\n const methodName = callee.name.text;\n if (methodName === \"find\" || methodName === \"getStore\" || methodName === \"get\") return true;\n }\n }\n\n // Flag: LHS is computed member expression x[key]\n if (ts.isElementAccessExpression(left)) return true;\n\n // Flag: LHS contains optional chaining (?.)\n if (hasOptionalChaining(left)) return true;\n\n return false;\n}\n\nfunction hasOptionalChaining(node: ts.Node): boolean {\n if (ts.isPropertyAccessExpression(node) && node.questionDotToken) return true;\n if (ts.isElementAccessExpression(node) && node.questionDotToken) return true;\n if (ts.isCallExpression(node) && node.questionDotToken) return true;\n if (ts.isPropertyAccessExpression(node)) return hasOptionalChaining(node.expression);\n if (ts.isCallExpression(node)) return hasOptionalChaining(node.expression);\n if (ts.isElementAccessExpression(node)) return hasOptionalChaining(node.expression);\n return false;\n}\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noNullTernaryNormalization: TSRule = {\n kind: \"ts\",\n id: \"no-null-ternary-normalization\",\n severity: \"warning\",\n message: \"Ternary null-normalization (x == null ? fallback : x); if the type guarantees non-null, remove the ternary; if not, fix the type upstream\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isConditionalExpression(node)) return;\n const test = node.condition;\n if (!ts.isBinaryExpression(test)) return;\n\n const op = test.operatorToken.kind;\n if (\n op !== ts.SyntaxKind.EqualsEqualsEqualsToken &&\n op !== ts.SyntaxKind.ExclamationEqualsEqualsToken &&\n op !== ts.SyntaxKind.EqualsEqualsToken &&\n op !== ts.SyntaxKind.ExclamationEqualsToken\n ) return;\n\n const hasNullishComparand = isNullish(test.left) || isNullish(test.right);\n if (!hasNullishComparand) return;\n\n if (isNullish(node.whenTrue) || isNullish(node.whenFalse)) {\n // Non-nullable tested value -> this is dead code\n const tested = isNullish(test.left) ? test.right : test.left;\n if (!ctx.isNullable(tested)) {\n ctx.report(node, \"Ternary null-normalization on a non-nullable type is dead code; remove the ternary\");\n return;\n }\n ctx.report(node);\n }\n },\n};\n\nfunction isNullish(node: ts.Node): boolean {\n if (node.kind === ts.SyntaxKind.NullKeyword) return true;\n if (ts.isIdentifier(node) && node.text === \"undefined\") return true;\n // void 0\n if (ts.isVoidExpression(node)) return true;\n return false;\n}\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noAnyCast: TSRule = {\n kind: \"ts\",\n id: \"no-any-cast\",\n severity: \"error\",\n message: \"Casting to `any` erases type safety; use a specific type or generic instead\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isAsExpression(node)) return;\n if (node.type.kind !== ts.SyntaxKind.AnyKeyword) return;\n ctx.report(node);\n },\n};\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noExplicitAnyAnnotation: TSRule = {\n kind: \"ts\",\n id: \"no-explicit-any-annotation\",\n severity: \"error\",\n message: \"Explicit `any` annotation erases type safety; use a specific type, `unknown`, or a generic\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (node.kind !== ts.SyntaxKind.AnyKeyword) return;\n if (node.parent && ts.isAsExpression(node.parent)) return;\n ctx.report(node);\n },\n};\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nfunction isTopLevelFunctionParam(source: string, offset: number): boolean {\n let depth = 0;\n for (let i = offset - 1; i >= 0; i--) {\n if (source[i] === \"}\") depth++;\n if (source[i] === \"{\") {\n if (depth === 0) {\n const before = source.slice(Math.max(0, i - 150), i).trimEnd();\n if (/(\\)|\\bfunction\\b.*\\))\\s*$/.test(before)) {\n depth--;\n continue;\n }\n return false;\n }\n depth--;\n }\n }\n return true;\n}\n\nexport const noInlineTypeInParams: TSRule = {\n kind: \"ts\",\n id: \"no-inline-type-in-params\",\n severity: \"info\",\n message: \"Inline type literal in annotation; extract to a named type for reuse and clarity\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isTypeLiteralNode(node)) return;\n // Parent should be a type annotation on a parameter\n const parent = node.parent;\n if (!parent || !ts.isParameter(parent)) return;\n if (parent.type !== node) return;\n\n const offset = node.getStart(ctx.sourceFile);\n if (!isTopLevelFunctionParam(ctx.source, offset)) return;\n ctx.report(node);\n },\n};\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noTypeAssertion: TSRule = {\n kind: \"ts\",\n id: \"no-type-assertion\",\n severity: \"error\",\n message: \"Double type assertion (`as unknown as T`) circumvents the type system; fix the upstream type or use a type guard\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isAsExpression(node)) return;\n if (!ts.isAsExpression(node.expression)) return;\n if (node.expression.type.kind !== ts.SyntaxKind.UnknownKeyword) return;\n ctx.report(node);\n },\n};\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\nimport { isNullishLiteral } from \"../../typecheck/utils.ts\";\n\nexport const noRedundantExistenceGuard: TSRule = {\n kind: \"ts\",\n id: \"no-redundant-existence-guard\",\n severity: \"warning\",\n message: \"Redundant existence guard (obj && obj.prop) on a non-nullable type; remove the guard or fix the type upstream\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isBinaryExpression(node)) return;\n if (node.operatorToken.kind !== ts.SyntaxKind.AmpersandAmpersandToken) return;\n\n const left = node.left;\n const right = node.right;\n\n // Pattern 1: obj && obj.prop / obj.method() / obj[key]\n if (ts.isIdentifier(left) && accessesIdentifier(right, left.text)) {\n if (ctx.isNullable(left)) return;\n ctx.report(node);\n return;\n }\n\n // Pattern 2: obj != null && obj.prop (or !== null, !== undefined)\n if (ts.isBinaryExpression(left) && isNullCheck(left)) {\n const checked = getNullCheckedIdentifier(left);\n if (checked && accessesIdentifier(right, checked)) {\n // The null-check LHS is the identifier — check its type before the comparison\n // If the type is non-nullable, the entire guard is redundant\n const identNode = ts.isIdentifier(left.left) ? left.left : left.right;\n if (ts.isIdentifier(identNode) && identNode.text === checked && !ctx.isNullable(identNode)) {\n ctx.report(node);\n }\n }\n }\n },\n};\n\n/** Check if the root object of `expr` is identifier `name`. */\nfunction accessesIdentifier(expr: ts.Node, name: string): boolean {\n const root = getExpressionRoot(expr);\n return ts.isIdentifier(root) && root.text === name;\n}\n\n/** Walk through property access, element access, and call chains to find the root expression. */\nfunction getExpressionRoot(node: ts.Node): ts.Node {\n if (ts.isPropertyAccessExpression(node)) return getExpressionRoot(node.expression);\n if (ts.isElementAccessExpression(node)) return getExpressionRoot(node.expression);\n if (ts.isCallExpression(node)) return getExpressionRoot(node.expression);\n return node;\n}\n\n/** Check if expr is a null/undefined comparison: x != null, x !== null, x !== undefined */\nfunction isNullCheck(expr: ts.BinaryExpression): boolean {\n const op = expr.operatorToken.kind;\n if (op !== ts.SyntaxKind.ExclamationEqualsToken && op !== ts.SyntaxKind.ExclamationEqualsEqualsToken) return false;\n return isNullishLiteral(expr.right) || isNullishLiteral(expr.left);\n}\n\n/** Get the identifier name being null-checked. */\nfunction getNullCheckedIdentifier(expr: ts.BinaryExpression): string | null {\n if (ts.isIdentifier(expr.left) && isNullishLiteral(expr.right)) return expr.left.text;\n if (ts.isIdentifier(expr.right) && isNullishLiteral(expr.left)) return expr.right.text;\n return null;\n}\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const preferDefaultParamValue: TSRule = {\n kind: \"ts\",\n id: \"prefer-default-param-value\",\n severity: \"info\",\n message: \"Use a default parameter value instead of reassigning from nullish coalescing inside the body\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isFunctionDeclaration(node) && !ts.isArrowFunction(node)) return;\n if (!node.body || !ts.isBlock(node.body)) return;\n const stmts = node.body.statements;\n if (stmts.length === 0) return;\n\n const firstStmt = stmts[0];\n if (firstStmt === undefined || !ts.isExpressionStatement(firstStmt)) return;\n const expr = firstStmt.expression;\n if (!ts.isBinaryExpression(expr) || expr.operatorToken.kind !== ts.SyntaxKind.EqualsToken) return;\n\n const right = expr.right;\n if (!ts.isBinaryExpression(right) || right.operatorToken.kind !== ts.SyntaxKind.QuestionQuestionToken) return;\n\n if (!ts.isIdentifier(expr.left) || !ts.isIdentifier(right.left)) return;\n if (expr.left.text !== right.left.text) return;\n\n const paramName = expr.left.text;\n const isParam = node.parameters.some((p) => ts.isIdentifier(p.name) && p.name.text === paramName);\n if (isParam) ctx.report(firstStmt);\n },\n};\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const preferRequiredParamWithGuard: TSRule = {\n kind: \"ts\",\n id: \"prefer-required-param-with-guard\",\n severity: \"info\",\n message: \"Optional param with immediate guard (if (!param) return/throw); make it required instead\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isFunctionDeclaration(node) && !ts.isArrowFunction(node)) return;\n if (!node.body || !ts.isBlock(node.body)) return;\n const stmts = node.body.statements;\n if (stmts.length === 0) return;\n\n const firstStmt = stmts[0];\n if (firstStmt === undefined || !ts.isIfStatement(firstStmt)) return;\n\n const test = firstStmt.expression;\n let guardedName: string | null = null;\n\n // Pattern 1: if (!param)\n if (ts.isPrefixUnaryExpression(test) && test.operator === ts.SyntaxKind.ExclamationToken) {\n if (ts.isIdentifier(test.operand)) guardedName = test.operand.text;\n }\n // Pattern 2: if (param === undefined)\n if (ts.isBinaryExpression(test)) {\n const op = test.operatorToken.kind;\n if (op === ts.SyntaxKind.EqualsEqualsEqualsToken || op === ts.SyntaxKind.EqualsEqualsToken) {\n if (ts.isIdentifier(test.left) && ts.isIdentifier(test.right) && test.right.text === \"undefined\") {\n guardedName = test.left.text;\n }\n }\n }\n\n if (!guardedName) return;\n\n const consequent = firstStmt.thenStatement;\n const isGuard =\n ts.isReturnStatement(consequent) ||\n ts.isThrowStatement(consequent) ||\n (ts.isBlock(consequent) &&\n consequent.statements.length === 1 &&\n consequent.statements[0] !== undefined &&\n (ts.isReturnStatement(consequent.statements[0]) || ts.isThrowStatement(consequent.statements[0])));\n\n if (!isGuard) return;\n\n const isOptional = node.parameters.some(\n (p) => ts.isIdentifier(p.name) && p.name.text === guardedName && p.questionToken !== undefined,\n );\n if (isOptional) ctx.report(firstStmt);\n },\n};\n","import type { CrossFileRule, Diagnostic, ProjectIndex } from \"../types.ts\";\n\nexport const duplicateTypeDeclaration: CrossFileRule = {\n id: \"duplicate-type-declaration\",\n severity: \"error\",\n message: \"Identical type shape declared in multiple files; consolidate to a single definition\",\n\n analyze(project: ProjectIndex): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n for (const group of project.types.getDuplicateGroups()) {\n // Only flag if types are in different files\n const files = new Set(group.map((e) => e.file));\n if (files.size < 2) continue;\n\n const sorted = [...group].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);\n for (const entry of sorted.slice(1)) {\n const others = sorted\n .filter((e) => e !== entry)\n .map((e) => `${e.name} (${e.file}:${e.line})`)\n .join(\", \");\n diagnostics.push({\n ruleId: this.id,\n severity: this.severity,\n message: `Type \"${entry.name}\" has identical shape to: ${others}`,\n file: entry.file,\n line: entry.line,\n column: 1,\n });\n }\n }\n return diagnostics;\n },\n};\n","import * as ts from \"typescript\";\nimport type { CrossFileRule, Diagnostic, ProjectIndex } from \"../types.ts\";\n\nexport const duplicateFunctionDeclaration: CrossFileRule = {\n id: \"duplicate-function-declaration\",\n severity: \"error\",\n message: \"Identical function body declared in multiple files; consolidate to a single definition\",\n\n analyze(project: ProjectIndex): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n for (const group of project.functions.getDuplicateGroups()) {\n const files = new Set(group.map((e) => e.file));\n if (files.size < 2) continue;\n\n // Skip setter pattern: single-assignment body (e.g. `botApi = api`)\n // These close over different module-scoped variables despite identical body text\n const first = group[0];\n if (first !== undefined && isSetter(first.node)) continue;\n\n const sorted = [...group].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);\n for (const entry of sorted.slice(1)) {\n const others = sorted\n .filter((e) => e !== entry)\n .map((e) => `${e.name} (${e.file}:${e.line})`)\n .join(\", \");\n diagnostics.push({\n ruleId: this.id,\n severity: this.severity,\n message: `Function \"${entry.name}\" has identical body to: ${others}`,\n file: entry.file,\n line: entry.line,\n column: 1,\n });\n }\n }\n return diagnostics;\n },\n};\n\nfunction isSetter(node: ts.Node): boolean {\n let body: ts.Block | undefined;\n if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {\n body = node.body;\n } else if (ts.isArrowFunction(node)) {\n body = ts.isBlock(node.body) ? node.body : undefined;\n }\n if (!body || body.statements.length !== 1) return false;\n const stmt = body.statements[0];\n if (stmt === undefined || !ts.isExpressionStatement(stmt)) return false;\n return ts.isBinaryExpression(stmt.expression) &&\n stmt.expression.operatorToken.kind === ts.SyntaxKind.EqualsToken;\n}\n","import type { CrossFileRule, Diagnostic, ProjectIndex } from \"../types.ts\";\n\nexport const optionalArgAlwaysUsed: CrossFileRule = {\n id: \"optional-arg-always-used\",\n severity: \"warning\",\n message: \"Optional parameter is always provided at every call site; make it required\",\n\n analyze(project: ProjectIndex): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n\n for (const fn of project.functions.getAll()) {\n // Find optional params (by index)\n for (let i = 0; i < fn.params.length; i++) {\n const param = fn.params[i];\n if (param === undefined) continue;\n if (!param.optional && !param.hasDefault) continue;\n\n // Find call sites for this function — use symbol matching when available\n const callSites = fn.symbol\n ? project.callSites.filter((c) => c.symbol === fn.symbol)\n : project.callSites.filter((c) => c.calleeName === fn.name);\n\n // Need at least 2 call sites to be meaningful\n if (callSites.length < 2) continue;\n\n // Check if every call site provides this positional argument\n const allProvide = callSites.every((c) => c.argCount > i);\n if (allProvide) {\n diagnostics.push({\n ruleId: this.id,\n severity: this.severity,\n message: `Optional parameter \"${param.name}\" is always provided at all ${callSites.length} call sites; make it required`,\n file: fn.file,\n line: fn.line,\n column: 1,\n });\n }\n }\n }\n\n return diagnostics;\n },\n};\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noCatchReturn: TSRule = {\n kind: \"ts\",\n id: \"no-catch-return\",\n severity: \"warning\",\n message:\n \"Catch block silently returns a fallback value with no logging; rethrow, log the error, or let it propagate\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isCatchClause(node)) return;\n const block = node.block;\n if (hasReturn(block) && !hasThrow(block) && !hasLogging(block)) {\n ctx.report(node);\n }\n },\n};\n\nfunction walkBlock(block: ts.Block, predicate: (node: ts.Node) => boolean): boolean {\n function visit(node: ts.Node): boolean {\n if (predicate(node)) return true;\n if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node)) return false;\n return ts.forEachChild(node, visit) ?? false;\n }\n return ts.forEachChild(block, visit) ?? false;\n}\n\nfunction hasReturn(block: ts.Block): boolean {\n return walkBlock(block, (n) => ts.isReturnStatement(n));\n}\n\nfunction hasThrow(block: ts.Block): boolean {\n return walkBlock(block, (n) => ts.isThrowStatement(n));\n}\n\nconst LOG_OBJECTS = new Set([\"console\", \"logger\", \"log\"]);\n\nfunction hasLogging(block: ts.Block): boolean {\n return walkBlock(block, (n) => {\n if (!ts.isCallExpression(n)) return false;\n const callee = n.expression;\n if (!ts.isPropertyAccessExpression(callee)) return false;\n if (!ts.isIdentifier(callee.expression)) return false;\n return LOG_OBJECTS.has(callee.expression.text);\n });\n}\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noErrorRewrap: TSRule = {\n kind: \"ts\",\n id: \"no-error-rewrap\",\n severity: \"error\",\n message:\n \"Re-wrapped error loses the original stack trace and type; use { cause: originalError } to preserve the error chain\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isCatchClause(node)) return;\n if (!node.variableDeclaration) return;\n const param = node.variableDeclaration.name;\n if (!ts.isIdentifier(param)) return;\n const catchName = param.text;\n findRewraps(node.block, catchName, ctx);\n },\n};\n\nfunction findRewraps(block: ts.Block, catchName: string, ctx: TSVisitContext): void {\n function visit(node: ts.Node): void {\n if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node) || ts.isArrowFunction(node)) return;\n if (ts.isThrowStatement(node) && node.expression && ts.isNewExpression(node.expression)) {\n const args = node.expression.arguments;\n if (args && args.length > 0 && referencesName(args, catchName) && !hasCauseArg(args)) {\n ctx.report(node);\n }\n return;\n }\n ts.forEachChild(node, visit);\n }\n ts.forEachChild(block, visit);\n}\n\nfunction referencesName(args: readonly ts.Expression[], name: string): boolean {\n for (const arg of args) {\n if (containsIdentifier(arg, name)) return true;\n }\n return false;\n}\n\nfunction containsIdentifier(node: ts.Node, name: string): boolean {\n if (ts.isIdentifier(node) && node.text === name) return true;\n return ts.forEachChild(node, (child) => containsIdentifier(child, name) || undefined) ?? false;\n}\n\nfunction hasCauseArg(args: readonly ts.Expression[]): boolean {\n for (const arg of args) {\n if (ts.isObjectLiteralExpression(arg)) {\n for (const prop of arg.properties) {\n if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name) && prop.name.text === \"cause\") return true;\n if (ts.isShorthandPropertyAssignment(prop) && prop.name.text === \"cause\") return true;\n }\n }\n }\n return false;\n}\n","import * as ts from \"typescript\";\nimport type { CrossFileRule, Diagnostic, ProjectIndex } from \"../types.ts\";\nimport { isNullishLiteral } from \"../../typecheck/utils.ts\";\n\nexport const explicitNullArg: CrossFileRule = {\n id: \"explicit-null-arg\",\n severity: \"warning\",\n message: \"Explicit null/undefined passed to a project function; consider redesigning the interface to not accept nullish values\",\n\n analyze(project: ProjectIndex): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n\n // Build lookup structures for known project functions\n const projectFnNames = new Set<string>();\n const projectFnSymbols = new Set<ts.Symbol>();\n for (const fn of project.functions.getAll()) {\n projectFnNames.add(fn.name);\n if (fn.symbol) projectFnSymbols.add(fn.symbol);\n }\n\n for (const site of project.callSites) {\n // Only flag calls to functions defined in the project — prefer symbol matching\n const isProjectFn = site.symbol\n ? projectFnSymbols.has(site.symbol)\n : projectFnNames.has(site.calleeName);\n if (!isProjectFn) continue;\n\n for (let i = 0; i < site.node.arguments.length; i++) {\n const arg = site.node.arguments[i];\n if (arg === undefined) continue;\n if (isNullishLiteral(arg)) {\n const val = arg.kind === ts.SyntaxKind.NullKeyword ? \"null\" : \"undefined\";\n diagnostics.push({\n ruleId: this.id,\n severity: this.severity,\n message: `Passing explicit ${val} to \"${site.calleeName}\" at argument ${i + 1}; consider redesigning the interface to not accept nullish values`,\n file: site.file,\n line: site.line,\n column: 1,\n });\n break; // one diagnostic per call site\n }\n }\n }\n\n return diagnostics;\n },\n};\n\n","import { dirname, resolve } from \"node:path\";\nimport type { CrossFileRule, Diagnostic, ProjectIndex } from \"../types.ts\";\n\nconst EXTENSIONS = [\".ts\", \".tsx\", \".mts\", \".cts\", \".js\", \".jsx\", \".mjs\", \".cjs\"];\n\nexport const duplicateFunctionName: CrossFileRule = {\n id: \"duplicate-function-name\",\n severity: \"error\",\n message: \"Same function name exported from multiple files; consolidate or rename to avoid ambiguity\",\n\n analyze(project: ProjectIndex): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n for (const group of project.functions.getNameCollisionGroups()) {\n // Skip groups already caught by duplicate-function-declaration (identical bodies)\n const hashes = new Set(group.map((e) => e.hash));\n if (hashes.size === 1) continue;\n\n // Skip wrapper/facade pattern: one file imports the function from another file in the group\n // Also handles barrel re-exports (A imports from barrel, barrel re-exports from B in group)\n const groupFiles = new Set(group.map((e) => e.file));\n const first = group[0];\n if (first === undefined) continue;\n const funcName = first.name;\n const hasImportLink = project.imports.some((imp) => {\n if (!groupFiles.has(imp.file)) return false;\n if (imp.importedName !== funcName && imp.localName !== funcName) return false;\n if (!imp.source.startsWith(\".\")) return false;\n const candidates = resolveCandidates(imp.file, imp.source);\n // Direct link: import resolves to a file in the group\n if (candidates.some((c) => groupFiles.has(c))) return true;\n // Barrel link: import resolves to an intermediary that re-exports from a group file\n return candidates.some((c) =>\n project.imports.some((reExp) => {\n if (reExp.file !== c) return false;\n if (reExp.importedName !== funcName && reExp.localName !== funcName) return false;\n if (!reExp.source.startsWith(\".\")) return false;\n const innerCandidates = resolveCandidates(reExp.file, reExp.source);\n return innerCandidates.some((ic) => groupFiles.has(ic));\n }),\n );\n });\n if (hasImportLink) continue;\n\n const sorted = [...group].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);\n for (const entry of sorted.slice(1)) {\n const others = sorted\n .filter((e) => e !== entry)\n .map((e) => `${e.file}:${e.line}`)\n .join(\", \");\n diagnostics.push({\n ruleId: this.id,\n severity: this.severity,\n message: `Exported function \"${entry.name}\" also defined in: ${others}`,\n file: entry.file,\n line: entry.line,\n column: 1,\n });\n }\n }\n return diagnostics;\n },\n};\n\nfunction resolveCandidates(fromFile: string, specifier: string): string[] {\n const base = resolve(dirname(fromFile), specifier);\n const candidates = [base];\n for (const ext of EXTENSIONS) {\n candidates.push(base + ext);\n candidates.push(resolve(base, \"index\" + ext));\n }\n return candidates;\n}\n","import * as ts from \"typescript\";\nimport type { CrossFileRule, Diagnostic, ProjectIndex } from \"../types.ts\";\n\nexport const duplicateTypeName: CrossFileRule = {\n id: \"duplicate-type-name\",\n severity: \"error\",\n message: \"Same type name exported from multiple files; consolidate or rename to avoid ambiguity\",\n\n analyze(project: ProjectIndex): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n for (const group of project.types.getNameCollisionGroups()) {\n // Skip groups already caught by duplicate-type-declaration (identical shapes)\n const hashes = new Set(group.map((e) => e.hash));\n if (hashes.size === 1) continue;\n\n // Skip if any entry is an inferred/reference type (Awaited<ReturnType<...>>, z.infer<...>, etc.)\n // rather than a structural definition — these are intentionally derived, not duplicated\n const hasInferredType = group.some(\n (e) => !ts.isTypeLiteralNode(e.node) && !ts.isInterfaceDeclaration(e.node),\n );\n if (hasInferredType) continue;\n\n const sorted = [...group].sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);\n for (const entry of sorted.slice(1)) {\n const others = sorted\n .filter((e) => e !== entry)\n .map((e) => `${e.file}:${e.line}`)\n .join(\", \");\n diagnostics.push({\n ruleId: this.id,\n severity: this.severity,\n message: `Exported type \"${entry.name}\" also defined in: ${others}`,\n file: entry.file,\n line: entry.line,\n column: 1,\n });\n }\n }\n return diagnostics;\n },\n};\n","import * as ts from \"typescript\";\nimport type { TSRule, TSVisitContext } from \"../types.ts\";\n\nexport const noDynamicImport: TSRule = {\n kind: \"ts\",\n id: \"no-dynamic-import\",\n severity: \"error\",\n message: \"Dynamic import() breaks static analysis and hides dependencies; use a static import instead\",\n\n visit(node: ts.Node, ctx: TSVisitContext) {\n if (!ts.isCallExpression(node)) return;\n if (node.expression.kind !== ts.SyntaxKind.ImportKeyword) return;\n ctx.report(node);\n },\n};\n","import type { Rule } from \"./types.ts\";\n\nimport { noEmptyCatch } from \"./ts/no-empty-catch.ts\";\nimport { noNonNullAssertion } from \"./ts/no-non-null-assertion.ts\";\nimport { noDoubleNegationCoercion } from \"./ts/no-double-negation-coercion.ts\";\nimport { noTsIgnore } from \"./ts/no-ts-ignore.ts\";\nimport { noNullishCoalescing } from \"./ts/no-nullish-coalescing.ts\";\nimport { noOptionalCall } from \"./ts/no-optional-call.ts\";\nimport { noOptionalPropertyAccess } from \"./ts/no-optional-property-access.ts\";\nimport { noOptionalElementAccess } from \"./ts/no-optional-element-access.ts\";\nimport { noLogicalOrFallback } from \"./ts/no-logical-or-fallback.ts\";\nimport { noNullTernaryNormalization } from \"./ts/no-null-ternary-normalization.ts\";\nimport { noAnyCast } from \"./ts/no-any-cast.ts\";\nimport { noExplicitAnyAnnotation } from \"./ts/no-explicit-any-annotation.ts\";\nimport { noInlineTypeInParams } from \"./ts/no-inline-type-in-params.ts\";\nimport { noTypeAssertion } from \"./ts/no-type-assertion.ts\";\nimport { noRedundantExistenceGuard } from \"./ts/no-redundant-existence-guard.ts\";\nimport { preferDefaultParamValue } from \"./ts/prefer-default-param-value.ts\";\nimport { preferRequiredParamWithGuard } from \"./ts/prefer-required-param-with-guard.ts\";\nimport { duplicateTypeDeclaration } from \"./cross-file/duplicate-type-declaration.ts\";\nimport { duplicateFunctionDeclaration } from \"./cross-file/duplicate-function-declaration.ts\";\nimport { optionalArgAlwaysUsed } from \"./cross-file/optional-arg-always-used.ts\";\nimport { noCatchReturn } from \"./ts/no-catch-return.ts\";\nimport { noErrorRewrap } from \"./ts/no-error-rewrap.ts\";\nimport { explicitNullArg } from \"./cross-file/explicit-null-arg.ts\";\nimport { duplicateFunctionName } from \"./cross-file/duplicate-function-name.ts\";\nimport { duplicateTypeName } from \"./cross-file/duplicate-type-name.ts\";\nimport { noDynamicImport } from \"./ts/no-dynamic-import.ts\";\n\nexport const allRules: Rule[] = [\n noEmptyCatch,\n noNonNullAssertion,\n noDoubleNegationCoercion,\n noTsIgnore,\n noNullishCoalescing,\n noOptionalCall,\n noOptionalPropertyAccess,\n noOptionalElementAccess,\n noLogicalOrFallback,\n noNullTernaryNormalization,\n noAnyCast,\n noExplicitAnyAnnotation,\n noInlineTypeInParams,\n noTypeAssertion,\n noRedundantExistenceGuard,\n preferDefaultParamValue,\n preferRequiredParamWithGuard,\n duplicateTypeDeclaration,\n duplicateFunctionDeclaration,\n optionalArgAlwaysUsed,\n noCatchReturn,\n noErrorRewrap,\n explicitNullArg,\n duplicateFunctionName,\n duplicateTypeName,\n noDynamicImport,\n];\n","import fg from \"fast-glob\";\nimport { readFileSync } from \"node:fs\";\nimport type { Diagnostic, CrossFileRule } from \"./rules/types.ts\";\nimport { allRules } from \"./rules/index.ts\";\nimport { isTSRule } from \"./rules/types.ts\";\nimport { collectProject, type CommentInfo } from \"./collect/index.ts\";\nimport { createProgramFromFiles } from \"./typecheck/program.ts\";\nimport { runTSRules } from \"./typecheck/walk.ts\";\n\nexport interface ScanOptions {\n paths: string[];\n strict?: boolean;\n rules?: string[];\n}\n\nexport interface ScanResult {\n diagnostics: Diagnostic[];\n fileCount: number;\n}\n\nexport async function scan(options: ScanOptions): Promise<ScanResult> {\n const patterns = options.paths.length > 0 ? options.paths : [\".\"];\n const globs = patterns.map((p) => {\n if (p === \".\") return `./**/*.{ts,cts,mts,tsx}`;\n if (p.endsWith(\"/\")) return `${p}**/*.{ts,cts,mts,tsx}`;\n if (!p.includes(\"*\") && !p.endsWith(\".ts\") && !p.endsWith(\".tsx\") && !p.endsWith(\".cts\") && !p.endsWith(\".mts\")) {\n return `${p}/**/*.{ts,cts,mts,tsx}`;\n }\n return p;\n });\n\n const files = await fg(globs, {\n ignore: [\"**/node_modules/**\", \"**/dist/**\", \"**/.git/**\", \"**/*.d.ts\", \"**/*.d.cts\", \"**/*.d.mts\"],\n absolute: true,\n });\n\n const activeRules = allRules.filter((r) => {\n if (options.rules && !options.rules.includes(r.id)) return false;\n return true;\n });\n\n const tsRules = activeRules.filter(isTSRule);\n const crossFileRules = activeRules.filter((r): r is CrossFileRule => !isTSRule(r));\n const diagnostics: Diagnostic[] = [];\n\n // Create program once — used by both TS rules and cross-file collect\n const program = files.length > 0 ? createProgramFromFiles(files) : null;\n\n // TS rule pass: run type-aware rules per file\n if (tsRules.length > 0 && program) {\n const checker = program.getTypeChecker();\n for (const file of files) {\n const source = readFileSync(file, \"utf8\");\n const sourceFile = program.getSourceFile(file);\n if (sourceFile) {\n const tsDiags = runTSRules(tsRules, sourceFile, checker, source, file);\n diagnostics.push(...tsDiags);\n }\n }\n }\n\n // Cross-file pass: collect project index and run analysis rules\n if (crossFileRules.length > 0 && program) {\n const projectIndex = collectProject(program);\n for (const rule of crossFileRules) {\n const crossDiags = rule.analyze(projectIndex);\n for (const d of crossDiags) {\n const fileData = projectIndex.files.get(d.file);\n if (fileData !== undefined) annotate([d], fileData.comments, fileData.source);\n }\n diagnostics.push(...crossDiags);\n }\n }\n\n const seen = new Set<string>();\n const deduped: Diagnostic[] = [];\n for (const d of diagnostics) {\n const key = `${d.file}:${d.line}:${d.ruleId}`;\n if (seen.has(key)) continue;\n seen.add(key);\n deduped.push(d);\n }\n\n if (options.strict) {\n for (const d of deduped) {\n d.severity = \"error\";\n }\n }\n\n return { diagnostics: deduped, fileCount: files.length };\n}\n\n/**\n * Attach annotations from comments to diagnostics.\n * A comment annotates a diagnostic if it ends on the line immediately above.\n * Consecutive line comments are joined into a single annotation.\n */\nfunction annotate(diagnostics: Diagnostic[], comments: CommentInfo[], source: string): void {\n if (comments.length === 0 || diagnostics.length === 0) return;\n\n const byEndLine = new Map<number, CommentInfo[]>();\n for (const c of comments) {\n const endLine = lineAt(source, c.end);\n let list = byEndLine.get(endLine);\n if (list === undefined) {\n list = [];\n byEndLine.set(endLine, list);\n }\n list.push(c);\n }\n\n for (const d of diagnostics) {\n const inline = findInlineComment(d.line, byEndLine);\n if (inline !== null) {\n d.annotation = inline;\n continue;\n }\n const above = collectAnnotation(d.line - 1, byEndLine, source);\n if (above !== null) d.annotation = above;\n }\n}\n\nfunction findInlineComment(diagLine: number, byEndLine: Map<number, CommentInfo[]>): string | null {\n const commentsOnLine = byEndLine.get(diagLine);\n if (commentsOnLine === undefined || commentsOnLine.length === 0) return null;\n const comment = commentsOnLine.at(-1);\n if (comment === undefined) return null;\n if (comment.type !== \"Line\") return null;\n const text = comment.value.trim();\n if (text.startsWith(\"@expect\")) return null;\n return text;\n}\n\nfunction collectAnnotation(\n commentEndLine: number,\n byEndLine: Map<number, CommentInfo[]>,\n source: string,\n): string | null {\n const commentsOnLine = byEndLine.get(commentEndLine);\n if (commentsOnLine === undefined || commentsOnLine.length === 0) return null;\n const comment = commentsOnLine.at(-1);\n if (comment === undefined) return null;\n\n if (comment.type === \"Block\") {\n return cleanBlockComment(comment.value);\n }\n\n const lines: string[] = [comment.value.trim()];\n let prevLine = commentEndLine - 1;\n for (;;) {\n const prev = byEndLine.get(prevLine);\n if (prev === undefined || prev.length === 0) break;\n const prevComment = prev.at(-1);\n if (prevComment === undefined || prevComment.type !== \"Line\") break;\n if (lineAt(source, prevComment.start) !== prevLine) break;\n lines.unshift(prevComment.value.trim());\n prevLine--;\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction cleanBlockComment(value: string): string {\n return value\n .split(\"\\n\")\n .map((line) => line.replace(/^\\s*\\*\\s?/, \"\").trim())\n .filter((line) => line.length > 0)\n .join(\"\\n\");\n}\n\nfunction lineAt(source: string, offset: number): number {\n let line = 1;\n for (let i = 0; i < offset && i < source.length; i++) {\n if (source[i] === \"\\n\") line++;\n }\n return line;\n}\n","import type * as ts from \"typescript\";\n\nexport interface Diagnostic {\n ruleId: string;\n severity: \"info\" | \"warning\" | \"error\";\n message: string;\n file: string;\n line: number;\n column: number;\n annotation?: string;\n}\n\nexport interface TSVisitContext {\n report(node: ts.Node, message?: string): void;\n reportAtOffset(offset: number, message?: string): void;\n filename: string;\n source: string;\n sourceFile: ts.SourceFile;\n checker: ts.TypeChecker;\n isNullable(node: ts.Node): boolean;\n isExternal(node: ts.Node): boolean;\n}\n\nexport interface TSRule {\n kind: \"ts\";\n id: string;\n severity: \"info\" | \"warning\" | \"error\";\n message: string;\n visit(node: ts.Node, ctx: TSVisitContext): void;\n}\n\nimport type { ProjectIndex } from \"../collect/index.ts\";\nexport type { ProjectIndex };\n\nexport interface CrossFileRule {\n id: string;\n severity: \"info\" | \"warning\" | \"error\";\n message: string;\n analyze(project: ProjectIndex): Diagnostic[];\n}\n\nexport type Rule = CrossFileRule | TSRule;\n\nexport function isTSRule(r: Rule): r is TSRule {\n return \"kind\" in r && r.kind === \"ts\";\n}\n","import * as ts from \"typescript\";\nimport { readFileSync } from \"node:fs\";\nimport { TypeRegistry } from \"./type-registry.ts\";\nimport { FunctionRegistry, type ParamInfo } from \"./function-registry.ts\";\nimport { hashFunctionBody } from \"../utils/hash.ts\";\n\nexport interface CommentInfo {\n type: \"Line\" | \"Block\";\n value: string;\n start: number;\n end: number;\n}\n\nexport interface ProjectIndex {\n types: TypeRegistry;\n functions: FunctionRegistry;\n callSites: CallSite[];\n imports: ImportEntry[];\n files: Map<string, { source: string; sourceFile: ts.SourceFile; comments: CommentInfo[] }>;\n}\n\nexport interface CallSite {\n calleeName: string;\n file: string;\n line: number;\n argCount: number;\n node: ts.CallExpression;\n symbol?: ts.Symbol;\n}\n\nexport interface ImportEntry {\n file: string;\n localName: string;\n importedName: string;\n source: string;\n}\n\nexport function collectProject(program: ts.Program): ProjectIndex {\n const checker = program.getTypeChecker();\n const types = new TypeRegistry();\n const functions = new FunctionRegistry();\n const callSites: CallSite[] = [];\n const imports: ImportEntry[] = [];\n const fileMap = new Map<string, { source: string; sourceFile: ts.SourceFile; comments: CommentInfo[] }>();\n\n for (const sourceFile of program.getSourceFiles()) {\n const file = sourceFile.fileName;\n // Skip declaration files and node_modules\n if (sourceFile.isDeclarationFile) continue;\n if (file.includes(\"node_modules\")) continue;\n\n const source = sourceFile.getFullText();\n const comments = collectAllComments(sourceFile);\n fileMap.set(file, { source, sourceFile, comments });\n\n function visit(node: ts.Node): void {\n collectTypes(node, file, sourceFile, types);\n collectFunctions(node, file, sourceFile, checker, functions);\n collectCallSites(node, file, sourceFile, checker, callSites);\n collectImports(node, file, imports);\n ts.forEachChild(node, visit);\n }\n ts.forEachChild(sourceFile, visit);\n }\n\n return { types, functions, callSites, imports, files: fileMap };\n}\n\nfunction isExported(node: ts.Node): boolean {\n if (!ts.canHaveModifiers(node)) return false;\n const mods = ts.getModifiers(node);\n if (mods === undefined) return false;\n return mods.some((m) => m.kind === ts.SyntaxKind.ExportKeyword);\n}\n\nfunction collectTypes(node: ts.Node, file: string, sourceFile: ts.SourceFile, registry: TypeRegistry): void {\n if (ts.isTypeAliasDeclaration(node)) {\n const line = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;\n registry.add(node.name.text, file, line, node.type, sourceFile, isExported(node));\n }\n if (ts.isInterfaceDeclaration(node)) {\n const line = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;\n registry.add(node.name.text, file, line, node, sourceFile, isExported(node));\n }\n}\n\nfunction collectFunctions(node: ts.Node, file: string, sourceFile: ts.SourceFile, checker: ts.TypeChecker, registry: FunctionRegistry): void {\n if (ts.isFunctionDeclaration(node) && node.name && node.body) {\n const line = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;\n const params = extractParams(node.parameters, sourceFile);\n const hash = hashFunctionBody(node.body, sourceFile);\n const symbol = checker.getSymbolAtLocation(node.name);\n registry.add({ name: node.name.text, file, line, hash, params, node, exported: isExported(node), symbol });\n }\n\n // Arrow functions assigned to const: const foo = (...) => { ... }\n if (ts.isVariableStatement(node)) {\n const exported = isExported(node);\n for (const decl of node.declarationList.declarations) {\n if (decl.initializer && ts.isArrowFunction(decl.initializer) && ts.isIdentifier(decl.name)) {\n const arrow = decl.initializer;\n const body = ts.isBlock(arrow.body) ? arrow.body : arrow.body;\n const line = ts.getLineAndCharacterOfPosition(sourceFile, decl.getStart(sourceFile)).line + 1;\n const params = extractParams(arrow.parameters, sourceFile);\n const hash = hashFunctionBody(body, sourceFile);\n const symbol = checker.getSymbolAtLocation(decl.name);\n registry.add({ name: decl.name.text, file, line, hash, params, node: arrow, exported, symbol });\n }\n }\n }\n}\n\nfunction extractParams(parameters: ts.NodeArray<ts.ParameterDeclaration>, sourceFile: ts.SourceFile): ParamInfo[] {\n return parameters.map((p) => {\n const name = p.name.getText(sourceFile);\n const optional = p.questionToken !== undefined;\n const hasDefault = p.initializer !== undefined;\n const typeText = p.type ? p.type.getText(sourceFile) : null;\n return { name, optional, hasDefault, typeText };\n });\n}\n\nfunction collectImports(node: ts.Node, file: string, imports: ImportEntry[]): void {\n if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {\n const moduleSource = node.moduleSpecifier.text;\n const clause = node.importClause;\n if (!clause) return;\n\n // Default import\n if (clause.name) {\n imports.push({\n file,\n localName: clause.name.text,\n importedName: \"default\",\n source: moduleSource,\n });\n }\n\n // Named imports\n if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {\n for (const el of clause.namedBindings.elements) {\n imports.push({\n file,\n localName: el.name.text,\n importedName: el.propertyName ? el.propertyName.text : el.name.text,\n source: moduleSource,\n });\n }\n }\n }\n\n // Re-exports: export { x } from \"./mod\"\n if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {\n const moduleSource = node.moduleSpecifier.text;\n if (node.exportClause && ts.isNamedExports(node.exportClause)) {\n for (const el of node.exportClause.elements) {\n imports.push({\n file,\n localName: el.name.text,\n importedName: el.propertyName ? el.propertyName.text : el.name.text,\n source: moduleSource,\n });\n }\n }\n }\n}\n\nfunction collectCallSites(node: ts.Node, file: string, sourceFile: ts.SourceFile, checker: ts.TypeChecker, sites: CallSite[]): void {\n if (!ts.isCallExpression(node)) return;\n let calleeName: string | null = null;\n if (ts.isIdentifier(node.expression)) {\n calleeName = node.expression.text;\n } else if (ts.isPropertyAccessExpression(node.expression)) {\n calleeName = node.expression.name.text;\n }\n if (calleeName) {\n const line = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)).line + 1;\n // Resolve the symbol the call refers to (follows imports to the declaration)\n let symbol: ts.Symbol | undefined;\n try {\n symbol = checker.getSymbolAtLocation(node.expression);\n if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {\n symbol = checker.getAliasedSymbol(symbol);\n }\n } catch {\n // Symbol resolution can fail on synthetic nodes\n }\n sites.push({\n calleeName,\n file,\n line,\n argCount: node.arguments.length,\n node,\n symbol,\n });\n }\n}\n\n/** Collect all comments from a source file. */\nexport function collectAllComments(sourceFile: ts.SourceFile): CommentInfo[] {\n const comments: CommentInfo[] = [];\n const source = sourceFile.getFullText();\n const seen = new Set<number>();\n\n function visit(node: ts.Node): void {\n const leading = ts.getLeadingCommentRanges(source, node.getFullStart());\n if (leading) {\n for (const r of leading) {\n if (seen.has(r.pos)) continue;\n seen.add(r.pos);\n const isLine = r.kind === ts.SyntaxKind.SingleLineCommentTrivia;\n comments.push({\n type: isLine ? \"Line\" : \"Block\",\n value: source.slice(r.pos + 2, isLine ? r.end : r.end - 2),\n start: r.pos,\n end: r.end,\n });\n }\n }\n const trailing = ts.getTrailingCommentRanges(source, node.getEnd());\n if (trailing) {\n for (const r of trailing) {\n if (seen.has(r.pos)) continue;\n seen.add(r.pos);\n const isLine = r.kind === ts.SyntaxKind.SingleLineCommentTrivia;\n comments.push({\n type: isLine ? \"Line\" : \"Block\",\n value: source.slice(r.pos + 2, isLine ? r.end : r.end - 2),\n start: r.pos,\n end: r.end,\n });\n }\n }\n ts.forEachChild(node, visit);\n }\n visit(sourceFile);\n\n return comments;\n}\n","import { createHash } from \"node:crypto\";\nimport * as ts from \"typescript\";\n\n/**\n * Create a structural hash of a type node.\n * Normalizes by sorting property names and stripping locations.\n */\nexport function hashTypeShape(node: ts.Node, sourceFile: ts.SourceFile): string {\n const normalized = normalizeTypeNode(node, sourceFile);\n return createHash(\"sha256\").update(normalized).digest(\"hex\").slice(0, 16);\n}\n\nfunction normalizeTypeNode(node: ts.Node, sourceFile: ts.SourceFile): string {\n if (ts.isTypeLiteralNode(node)) {\n const normalized = node.members.map((m) => normalizeTypeNode(m, sourceFile)).sort().join(\";\");\n return `{${normalized}}`;\n }\n if (ts.isInterfaceDeclaration(node)) {\n const normalized = node.members.map((m) => normalizeTypeNode(m, sourceFile)).sort().join(\";\");\n return `{${normalized}}`;\n }\n if (ts.isPropertySignature(node)) {\n const keyName = node.name.getText(sourceFile);\n const optional = node.questionToken ? \"?\" : \"\";\n const type = node.type ? normalizeTypeNode(node.type, sourceFile) : \"any\";\n return `${keyName}${optional}:${type}`;\n }\n if (ts.isTypeAliasDeclaration(node)) {\n return normalizeTypeNode(node.type, sourceFile);\n }\n // Fallback: use source text with whitespace normalized\n return node.getText(sourceFile).replace(/\\s+/g, \" \").trim();\n}\n\n/**\n * Hash a function body for duplicate detection.\n * Normalizes whitespace.\n */\nexport function hashFunctionBody(node: ts.Node, sourceFile: ts.SourceFile): string {\n const bodyText = node.getText(sourceFile);\n const normalized = bodyText.replace(/\\s+/g, \" \").trim();\n return createHash(\"sha256\").update(normalized).digest(\"hex\").slice(0, 16);\n}\n","import type * as ts from \"typescript\";\nimport { hashTypeShape } from \"../utils/hash.ts\";\n\nexport interface TypeEntry {\n name: string;\n file: string;\n line: number;\n hash: string;\n node: ts.Node;\n exported: boolean;\n}\n\nexport class TypeRegistry {\n private entries: TypeEntry[] = [];\n private byHash = new Map<string, TypeEntry[]>();\n\n add(name: string, file: string, line: number, typeNode: ts.Node, sourceFile: ts.SourceFile, exported: boolean): void {\n const hash = hashTypeShape(typeNode, sourceFile);\n const entry: TypeEntry = { name, file, line, hash, node: typeNode, exported };\n this.entries.push(entry);\n let list = this.byHash.get(hash);\n if (list === undefined) {\n list = [];\n this.byHash.set(hash, list);\n }\n list.push(entry);\n }\n\n getDuplicateGroups(): TypeEntry[][] {\n return [...this.byHash.values()].filter((group) => group.length > 1);\n }\n\n getAll(): TypeEntry[] {\n return this.entries;\n }\n\n getNameCollisionGroups(): TypeEntry[][] {\n const byName = new Map<string, TypeEntry[]>();\n for (const entry of this.entries) {\n if (!entry.exported) continue;\n let list = byName.get(entry.name);\n if (list === undefined) {\n list = [];\n byName.set(entry.name, list);\n }\n list.push(entry);\n }\n return [...byName.values()].filter((group) => {\n if (group.length < 2) return false;\n const files = new Set(group.map((e) => e.file));\n return files.size > 1;\n });\n }\n}\n","import type * as ts from \"typescript\";\n\nexport interface ParamInfo {\n name: string;\n optional: boolean;\n hasDefault: boolean;\n typeText: string | null;\n}\n\nexport interface FunctionEntry {\n name: string;\n file: string;\n line: number;\n hash: string;\n params: ParamInfo[];\n node: ts.Node;\n exported: boolean;\n symbol?: ts.Symbol;\n}\n\nexport class FunctionRegistry {\n private entries: FunctionEntry[] = [];\n private byHash = new Map<string, FunctionEntry[]>();\n\n add(entry: FunctionEntry): void {\n this.entries.push(entry);\n let list = this.byHash.get(entry.hash);\n if (list === undefined) {\n list = [];\n this.byHash.set(entry.hash, list);\n }\n list.push(entry);\n }\n\n getDuplicateGroups(): FunctionEntry[][] {\n return [...this.byHash.values()].filter((group) => group.length > 1);\n }\n\n getAll(): FunctionEntry[] {\n return this.entries;\n }\n\n getByName(name: string): FunctionEntry[] {\n return this.entries.filter((e) => e.name === name);\n }\n\n getNameCollisionGroups(): FunctionEntry[][] {\n const byName = new Map<string, FunctionEntry[]>();\n for (const entry of this.entries) {\n if (!entry.exported) continue;\n let list = byName.get(entry.name);\n if (list === undefined) {\n list = [];\n byName.set(entry.name, list);\n }\n list.push(entry);\n }\n return [...byName.values()].filter((group) => {\n if (group.length < 2) return false;\n const files = new Set(group.map((e) => e.file));\n return files.size > 1;\n });\n }\n}\n","import * as ts from \"typescript\";\nimport { dirname } from \"node:path\";\n\nexport function createProgramFromFiles(files: string[]): ts.Program {\n let configPath: string | undefined;\n if (files.length > 0) {\n configPath = ts.findConfigFile(dirname(files[0] as string), ts.sys.fileExists, \"tsconfig.json\");\n }\n\n if (configPath) {\n const configFile = ts.readConfigFile(configPath, ts.sys.readFile);\n const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, dirname(configPath));\n return ts.createProgram({\n rootNames: files,\n options: { ...parsed.options, skipLibCheck: true },\n });\n }\n\n return ts.createProgram({\n rootNames: files,\n options: {\n target: ts.ScriptTarget.ESNext,\n module: ts.ModuleKind.ESNext,\n moduleResolution: ts.ModuleResolutionKind.Bundler,\n strict: true,\n skipLibCheck: true,\n noEmit: true,\n },\n });\n}\n","import * as ts from \"typescript\";\nimport type { Diagnostic, TSRule, TSVisitContext } from \"../rules/types.ts\";\nimport { isNullableType, isFromNodeModules } from \"./utils.ts\";\n\nexport function runTSRules(\n rules: TSRule[],\n sourceFile: ts.SourceFile,\n checker: ts.TypeChecker,\n source: string,\n filename: string,\n): Diagnostic[] {\n const diagnostics: Diagnostic[] = [];\n\n const contexts = rules.map((rule) => ({\n rule,\n ctx: buildContext(rule, sourceFile, checker, source, filename, diagnostics),\n }));\n\n function visit(node: ts.Node): void {\n for (const { rule, ctx } of contexts) {\n rule.visit(node, ctx);\n }\n ts.forEachChild(node, visit);\n }\n\n visit(sourceFile);\n return diagnostics;\n}\n\nfunction buildContext(\n rule: TSRule,\n sourceFile: ts.SourceFile,\n checker: ts.TypeChecker,\n source: string,\n filename: string,\n diagnostics: Diagnostic[],\n): TSVisitContext {\n return {\n filename,\n source,\n sourceFile,\n checker,\n\n report(node: ts.Node, message?: string) {\n const { line, character } = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile));\n diagnostics.push({\n ruleId: rule.id,\n severity: rule.severity,\n message: message ?? rule.message,\n file: filename,\n line: line + 1,\n column: character + 1,\n });\n },\n\n reportAtOffset(offset: number, message?: string) {\n const { line, character } = ts.getLineAndCharacterOfPosition(sourceFile, offset);\n diagnostics.push({\n ruleId: rule.id,\n severity: rule.severity,\n message: message ?? rule.message,\n file: filename,\n line: line + 1,\n column: character + 1,\n });\n },\n\n isNullable(node: ts.Node): boolean {\n const type = checker.getTypeAtLocation(node);\n return isNullableType(checker, type);\n },\n\n isExternal(node: ts.Node): boolean {\n const type = checker.getTypeAtLocation(node);\n const symbol = type.getSymbol();\n if (!symbol) return false;\n const declarations = symbol.getDeclarations();\n if (!declarations || declarations.length === 0) return false;\n return declarations.some((d) => isFromNodeModules(d));\n },\n };\n}\n"],"mappings":";AAAA,YAAY,QAAQ;AAGb,IAAM,eAAuB;AAAA,EAClC,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,iBAAc,IAAI,EAAG;AAC7B,UAAM,QAAQ,KAAK;AACnB,QAAI,MAAM,WAAW,SAAS,EAAG;AAGjC,UAAM,aAAa,MAAM,SAAS,IAAI,UAAU;AAChD,UAAM,WAAW,MAAM,OAAO;AAC9B,UAAM,QAAQ,IAAI,OAAO,MAAM,aAAa,GAAG,WAAW,CAAC;AAC3D,QAAI,MAAM,SAAS,IAAI,KAAK,MAAM,SAAS,IAAI,EAAG;AAElD,QAAI,OAAO,IAAI;AAAA,EACjB;AACF;;;ACtBA,YAAYA,SAAQ;AAGb,IAAM,qBAA6B;AAAA,EACxC,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,wBAAoB,IAAI,EAAG;AAEnC,UAAM,QAAQ,KAAK;AAGnB,QAAI,CAAC,IAAI,WAAW,KAAK,EAAG;AAG5B,QAAI,IAAI,WAAW,KAAK,EAAG;AAG3B,QAAI,qBAAqB,KAAK,EAAG;AAGjC,QAAI,sBAAsB,KAAK,EAAG;AAGlC,QAAI,sBAAsB,KAAK,EAAG;AAElC,QAAI,OAAO,IAAI;AAAA,EACjB;AACF;AAEA,SAAS,qBAAqB,MAAwB;AACpD,MAAI,CAAI,8BAA0B,IAAI,EAAG,QAAO;AAChD,QAAM,MAAM,KAAK;AACjB,MAAI,CAAI,qBAAiB,GAAG,EAAG,QAAO;AACtC,QAAM,SAAS,IAAI;AACnB,MAAI,CAAI,+BAA2B,MAAM,EAAG,QAAO;AACnD,SAAO,OAAO,KAAK,SAAS;AAC9B;AAEA,SAAS,sBAAsB,MAAwB;AAErD,MAAO,8BAA0B,IAAI,GAAG;AACtC,UAAM,MAAM,KAAK;AACjB,QAAO,qBAAiB,GAAG,GAAG;AAC5B,YAAM,SAAS,IAAI;AACnB,UAAO,+BAA2B,MAAM,KAAK,OAAO,KAAK,SAAS,SAAU,QAAO;AAAA,IACrF;AAEA,QAAO,iBAAa,GAAG,GAAG;AACxB,YAAM,OAAO,iBAAiB,GAAG;AACjC,UAAI,QAAW,qBAAiB,IAAI,GAAG;AACrC,cAAM,SAAS,KAAK;AACpB,YAAO,+BAA2B,MAAM,KAAK,OAAO,KAAK,SAAS,SAAU,QAAO;AAAA,MACrF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,sBAAsB,MAAwB;AACrD,MAAI,CAAI,8BAA0B,IAAI,EAAG,QAAO;AAChD,QAAM,MAAM,KAAK;AAEjB,QAAM,UAAU,kBAAkB,GAAG;AACrC,MAAI,CAAC,QAAS,QAAO;AAGrB,MAAI,yBAAyB,MAAM,OAAO,EAAG,QAAO;AAGpD,SAAO,wBAAwB,MAAM,OAAO;AAC9C;AAGA,SAAS,yBAAyB,MAAe,SAA0B;AACzE,MAAI,UAAmB;AACvB,SAAO,QAAQ,QAAQ;AACrB,cAAU,QAAQ;AAClB,QAAO,mBAAe,OAAO,KAAK,QAAQ,WAAW;AACnD,UAAI,uBAAuB,QAAQ,WAAW,OAAO,EAAG,QAAO;AAAA,IACjE;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,uBAAuB,MAAqB,SAA0B;AAC7E,MAAI,CAAI,uBAAmB,IAAI,EAAG,QAAO;AACzC,QAAM,KAAK,KAAK,cAAc;AAE9B,MAAI,OAAU,eAAW,iBAAiB,OAAU,eAAW,qBAAqB;AAClF,WAAO,eAAe,KAAK,OAAO,OAAO;AAAA,EAC3C;AAEA,MAAI,OAAU,eAAW,oBAAoB,OAAU,eAAW,wBAAwB;AACxF,WAAO,eAAe,KAAK,MAAM,OAAO;AAAA,EAC1C;AACA,SAAO;AACT;AAGA,SAAS,eAAe,MAAe,SAA0B;AAC/D,MAAI,CAAI,+BAA2B,IAAI,EAAG,QAAO;AACjD,MAAI,KAAK,KAAK,SAAS,SAAU,QAAO;AACxC,SAAO,kBAAkB,KAAK,UAAU,MAAM;AAChD;AAcA,SAAS,wBAAwB,MAAe,SAA0B;AAExE,MAAI,UAAmB;AACvB,SAAO,QAAQ,QAAQ;AACrB,UAAM,SAAS,QAAQ;AAGvB,QAAO,YAAQ,MAAM,GAAG;AACtB,iBAAW,QAAQ,OAAO,YAAY;AACpC,YAAI,SAAS,WAAW,KAAK,OAAO,QAAQ,IAAK;AACjD,YAAO,kBAAc,IAAI,KAAK,2BAA2B,MAAM,OAAO,EAAG,QAAO;AAAA,MAClF;AAAA,IACF;AAGA,QAAO,kBAAc,MAAM,KAAK,OAAO,kBAAkB,SAAS;AAChE,UAAI,sBAAsB,OAAO,YAAY,OAAO,EAAG,QAAO;AAAA,IAChE;AAEA,QAAO,YAAQ,OAAO,KAAQ,kBAAc,MAAM,KAAK,OAAO,kBAAkB,SAAS;AACvF,UAAI,sBAAsB,OAAO,YAAY,OAAO,EAAG,QAAO;AAAA,IAChE;AAEA,cAAU;AAAA,EACZ;AACA,SAAO;AACT;AAGA,SAAS,2BAA2B,MAAsB,SAA0B;AAClF,MAAI,CAAC,YAAY,KAAK,aAAa,EAAG,QAAO;AAC7C,SAAO,kBAAkB,KAAK,YAAY,OAAO;AACnD;AAGA,SAAS,kBAAkB,MAAqB,SAA0B;AACxE,MAAI,CAAI,uBAAmB,IAAI,EAAG,QAAO;AACzC,QAAM,KAAK,KAAK,cAAc;AAG9B,MAAK,OAAU,eAAW,2BAA2B,OAAU,eAAW,mBAAoB;AAC5F,QAAI,eAAe,KAAK,MAAM,OAAO,KAAK,sBAAsB,KAAK,OAAO,CAAC,EAAG,QAAO;AACvF,QAAI,eAAe,KAAK,OAAO,OAAO,KAAK,sBAAsB,KAAK,MAAM,CAAC,EAAG,QAAO;AAAA,EACzF;AAGA,MAAI,OAAU,eAAW,eAAe;AACtC,QAAI,eAAe,KAAK,MAAM,OAAO,KAAK,oBAAoB,KAAK,OAAO,CAAC,EAAG,QAAO;AAAA,EACvF;AAGA,MAAK,OAAU,eAAW,gCAAgC,OAAU,eAAW,wBAAyB;AACtG,QAAI,eAAe,KAAK,MAAM,OAAO,KAAK,oBAAoB,KAAK,OAAO,CAAC,EAAG,QAAO;AAAA,EACvF;AAEA,SAAO;AACT;AAGA,SAAS,sBAAsB,MAAqB,SAA0B;AAC5E,MAAI,CAAI,uBAAmB,IAAI,EAAG,QAAO;AACzC,QAAM,KAAK,KAAK,cAAc;AAG9B,MAAI,OAAU,eAAW,kBAAkB;AACzC,QAAI,eAAe,KAAK,MAAM,OAAO,KAAK,sBAAsB,KAAK,OAAO,CAAC,EAAG,QAAO;AAAA,EACzF;AAGA,MAAI,OAAU,eAAW,wBAAwB;AAC/C,QAAI,eAAe,KAAK,MAAM,OAAO,KAAK,oBAAoB,KAAK,OAAO,CAAC,EAAG,QAAO;AAAA,EACvF;AAGA,MAAK,OAAU,eAAW,gCAAgC,OAAU,eAAW,wBAAyB;AACtG,QAAI,eAAe,KAAK,MAAM,OAAO,KAAK,sBAAsB,KAAK,OAAO,CAAC,EAAG,QAAO;AAAA,EACzF;AAEA,SAAO;AACT;AAEA,SAAS,YAAY,MAA6B;AAChD,MAAO,sBAAkB,IAAI,KAAQ,qBAAiB,IAAI,EAAG,QAAO;AACpE,MAAO,YAAQ,IAAI,KAAK,KAAK,WAAW,WAAW,GAAG;AACpD,UAAM,QAAQ,KAAK,WAAW,CAAC;AAC/B,QAAI,UAAU,OAAW,QAAO;AAChC,WAAU,sBAAkB,KAAK,KAAQ,qBAAiB,KAAK;AAAA,EACjE;AACA,SAAO;AACT;AAEA,SAAS,sBAAsB,MAAe,OAAwB;AACpE,SAAU,qBAAiB,IAAI,KAAK,KAAK,SAAS,OAAO,KAAK;AAChE;AAEA,SAAS,oBAAoB,MAAe,KAAsB;AAChE,SAAU,qBAAiB,IAAI,KAAK,OAAO,KAAK,IAAI,KAAK;AAC3D;AAEA,SAAS,kBAAkB,MAA8B;AACvD,MAAO,iBAAa,IAAI,EAAG,QAAO,KAAK;AACvC,SAAO;AACT;AAEA,SAAS,iBAAiB,IAA8C;AACtE,QAAM,aAAa,GAAG,cAAc;AACpC,MAAI;AACJ,WAAS,MAAM,MAAqB;AAClC,QAAO,0BAAsB,IAAI,KAAQ,iBAAa,KAAK,IAAI,KAAK,KAAK,KAAK,SAAS,GAAG,QAAQ,KAAK,aAAa;AAClH,eAAS,KAAK;AAAA,IAChB;AACA,QAAI,CAAC,OAAQ,CAAG,iBAAa,MAAM,KAAK;AAAA,EAC1C;AACA,QAAM,UAAU;AAChB,SAAO;AACT;;;AC9OA,YAAYC,SAAQ;;;ACApB,YAAYC,SAAQ;AAEpB,SAAS,SAAS,OAAe,MAAuB;AACtD,UAAQ,QAAQ,UAAU;AAC5B;AAEA,IAAM,gBAAmB,cAAU,OAAU,cAAU,YAAe,cAAU;AAEzE,SAAS,eAAe,SAAyB,MAAwB;AAC9E,MAAI,KAAK,QAAQ,GAAG;AAClB,WAAO,KAAK,MAAM,KAAK,CAAC,MAAM,SAAS,EAAE,OAAO,aAAa,CAAC;AAAA,EAChE;AACA,SAAO,SAAS,KAAK,OAAO,aAAa;AAC3C;AAEO,SAAS,kBAAkB,MAAwB;AACxD,QAAM,aAAa,KAAK,cAAc;AACtC,SAAO,WAAW,SAAS,SAAS,gBAAgB;AACtD;AAEO,SAAS,mBAAmB,MAAwB;AACzD,MAAI,KAAK,QAAQ,GAAG;AAClB,WAAO,KAAK,MAAM,KAAK,CAAC,MAAM,SAAS,EAAE,OAAU,cAAU,UAAU,CAAC;AAAA,EAC1E;AACA,SAAO,SAAS,KAAK,OAAU,cAAU,UAAU;AACrD;AAEO,SAAS,oBAAoB,MAAwB;AAC1D,MAAI,KAAK,QAAQ,GAAG;AAClB,WAAO,KAAK,MAAM,KAAK,CAAC,MAAM,SAAS,EAAE,OAAU,cAAU,WAAW,CAAC;AAAA,EAC3E;AACA,SAAO,SAAS,KAAK,OAAU,cAAU,WAAW;AACtD;AAEO,SAAS,iBAAiB,MAAwB;AACvD,MAAI,KAAK,SAAY,eAAW,YAAa,QAAO;AACpD,MAAO,iBAAa,IAAI,KAAK,KAAK,SAAS,YAAa,QAAO;AAC/D,SAAO;AACT;;;ADlCO,IAAM,2BAAmC;AAAA,EAC9C,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,4BAAwB,IAAI,EAAG;AACvC,QAAI,KAAK,aAAgB,eAAW,iBAAkB;AACtD,UAAM,QAAQ,KAAK;AACnB,QAAI,CAAI,4BAAwB,KAAK,EAAG;AACxC,QAAI,MAAM,aAAgB,eAAW,iBAAkB;AAEvD,UAAM,UAAU,MAAM;AAGtB,UAAM,YAAY,IAAI,QAAQ,kBAAkB,OAAO;AACvD,QAAI,oBAAoB,SAAS,KAAK,EAAE,UAAU,QAAW,cAAU,QAAQ;AAC7E,UAAI,OAAO,MAAM,sEAAsE;AACvF;AAAA,IACF;AAGA,QAAI,oBAAoB,OAAO,EAAG;AAElC,QAAI,OAAO,IAAI;AAAA,EACjB;AACF;AAEA,IAAM,cAAc,oBAAI,IAAI;AAAA,EACvB,eAAW;AAAA,EACX,eAAW;AAAA,EACX,eAAW;AAAA,EACX,eAAW;AAAA,EACX,eAAW;AAAA,EACX,eAAW;AAChB,CAAC;AAED,SAAS,oBAAoB,MAAwB;AAEnD,MAAO,uBAAmB,IAAI,KAAK,YAAY,IAAI,KAAK,cAAc,IAAI,EAAG,QAAO;AAEpF,MAAO,8BAA0B,IAAI,EAAG,QAAO,oBAAoB,KAAK,UAAU;AAClF,SAAO;AACT;;;AEhDA,YAAYC,SAAQ;AAGb,IAAM,aAAqB;AAAA,EAChC,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,UAAM,SAAY,4BAAwB,IAAI,QAAQ,KAAK,aAAa,CAAC;AACzE,QAAI,CAAC,OAAQ;AACb,eAAW,SAAS,QAAQ;AAC1B,YAAM,OAAO,IAAI,OAAO,MAAM,MAAM,KAAK,MAAM,GAAG;AAClD,UAAI,KAAK,SAAS,YAAY,KAAK,KAAK,SAAS,kBAAkB,GAAG;AACpE,YAAI,eAAe,MAAM,GAAG;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AACF;;;ACnBA,YAAYC,SAAQ;AAGb,IAAM,sBAA8B;AAAA,EACzC,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,uBAAmB,IAAI,EAAG;AAClC,QAAI,KAAK,cAAc,SAAY,eAAW,sBAAuB;AACrE,QAAI,IAAI,WAAW,KAAK,IAAI,EAAG;AAC/B,QAAI,OAAO,IAAI;AAAA,EACjB;AACF;;;ACfA,YAAYC,SAAQ;AAGb,IAAM,iBAAyB;AAAA,EACpC,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,qBAAiB,IAAI,EAAG;AAChC,QAAI,CAAC,KAAK,iBAAkB;AAC5B,QAAI,IAAI,WAAW,KAAK,UAAU,EAAG;AACrC,QAAI,OAAO,IAAI;AAAA,EACjB;AACF;;;ACfA,YAAYC,SAAQ;AAGb,IAAM,2BAAmC;AAAA,EAC9C,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,+BAA2B,IAAI,EAAG;AAC1C,QAAI,CAAC,KAAK,iBAAkB;AAC5B,QAAI,IAAI,WAAW,KAAK,UAAU,EAAG;AACrC,QAAI,OAAO,IAAI;AAAA,EACjB;AACF;;;ACfA,YAAYC,SAAQ;AAGb,IAAM,0BAAkC;AAAA,EAC7C,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,8BAA0B,IAAI,EAAG;AACzC,QAAI,CAAC,KAAK,iBAAkB;AAC5B,QAAI,IAAI,WAAW,KAAK,UAAU,EAAG;AACrC,QAAI,OAAO,IAAI;AAAA,EACjB;AACF;;;ACfA,YAAYC,UAAQ;AAIb,IAAM,sBAA8B;AAAA,EACzC,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SACE;AAAA,EAEF,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,wBAAmB,IAAI,EAAG;AAClC,QAAI,KAAK,cAAc,SAAY,gBAAW,YAAa;AAE3D,UAAM,QAAQ,KAAK;AACnB,QAAI,CAAC,UAAU,KAAK,EAAG;AAEvB,UAAM,OAAO,KAAK;AAClB,UAAM,UAAU,IAAI,QAAQ,kBAAkB,IAAI;AAGlD,QAAI,sBAAsB,IAAI,EAAG;AAIjC,QAAI,oBAAoB,SAAS,IAAI,OAAO,EAAG;AAG/C,QAAI,mBAAmB,OAAO,KAAK,CAAC,cAAc,KAAK,GAAG;AACxD,UAAI,OAAO,MAAM,sEAAsE;AACvF;AAAA,IACF;AAGA,QAAI,sBAAsB,IAAI,GAAG;AAC/B,UAAI,OAAO,IAAI;AAAA,IACjB;AAAA,EACF;AACF;AAGA,SAAS,oBAAoB,MAAe,SAAkC;AAC5E,MAAI,eAAe,SAAS,IAAI,EAAG,QAAO;AAC1C,MAAI,KAAK,QAAQ,GAAG;AAClB,WAAO,KAAK,MAAM,MAAM,CAAC,OAAO,EAAE,QAAW,eAAU,gBAAgB,CAAC;AAAA,EAC1E;AACA,UAAQ,KAAK,QAAW,eAAU,gBAAgB;AACpD;AAEA,SAAS,UAAU,MAAwB;AACzC,MAAO,qBAAgB,IAAI,KAAQ,sBAAiB,IAAI,KAAQ,qCAAgC,IAAI,EAAG,QAAO;AAC9G,MAAO,0BAAqB,IAAI,EAAG,QAAO;AAC1C,MAAO,8BAAyB,IAAI,KAAQ,+BAA0B,IAAI,EAAG,QAAO;AACpF,MAAO,kBAAa,IAAI,KAAK,KAAK,SAAS,YAAa,QAAO;AAC/D,MAAI,KAAK,SAAY,gBAAW,YAAa,QAAO;AACpD,MAAI,KAAK,SAAY,gBAAW,eAAe,KAAK,SAAY,gBAAW,aAAc,QAAO;AAChG,SAAO;AACT;AAGA,SAAS,sBAAsB,MAAwB;AACrD,MAAI,CAAI,sBAAiB,IAAI,EAAG,QAAO;AACvC,MAAI,CAAI,kBAAa,KAAK,UAAU,EAAG,QAAO;AAC9C,QAAM,OAAO,KAAK,WAAW;AAC7B,SAAO,SAAS,YAAY,SAAS,cAAc,SAAS;AAC9D;AAEA,SAAS,cAAc,MAAwB;AAC7C,SAAU,sBAAiB,IAAI,KAAK,KAAK,SAAS;AACpD;AAEA,SAAS,sBAAsB,MAAwB;AAErD,MAAO,sBAAiB,IAAI,GAAG;AAC7B,UAAM,SAAS,KAAK;AACpB,QAAO,gCAA2B,MAAM,GAAG;AACzC,YAAM,aAAa,OAAO,KAAK;AAC/B,UAAI,eAAe,UAAU,eAAe,cAAc,eAAe,MAAO,QAAO;AAAA,IACzF;AAAA,EACF;AAGA,MAAO,+BAA0B,IAAI,EAAG,QAAO;AAG/C,MAAI,oBAAoB,IAAI,EAAG,QAAO;AAEtC,SAAO;AACT;AAEA,SAAS,oBAAoB,MAAwB;AACnD,MAAO,gCAA2B,IAAI,KAAK,KAAK,iBAAkB,QAAO;AACzE,MAAO,+BAA0B,IAAI,KAAK,KAAK,iBAAkB,QAAO;AACxE,MAAO,sBAAiB,IAAI,KAAK,KAAK,iBAAkB,QAAO;AAC/D,MAAO,gCAA2B,IAAI,EAAG,QAAO,oBAAoB,KAAK,UAAU;AACnF,MAAO,sBAAiB,IAAI,EAAG,QAAO,oBAAoB,KAAK,UAAU;AACzE,MAAO,+BAA0B,IAAI,EAAG,QAAO,oBAAoB,KAAK,UAAU;AAClF,SAAO;AACT;;;ACnGA,YAAYC,UAAQ;AAGb,IAAM,6BAAqC;AAAA,EAChD,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,6BAAwB,IAAI,EAAG;AACvC,UAAM,OAAO,KAAK;AAClB,QAAI,CAAI,wBAAmB,IAAI,EAAG;AAElC,UAAM,KAAK,KAAK,cAAc;AAC9B,QACE,OAAU,gBAAW,2BACrB,OAAU,gBAAW,gCACrB,OAAU,gBAAW,qBACrB,OAAU,gBAAW,uBACrB;AAEF,UAAM,sBAAsB,UAAU,KAAK,IAAI,KAAK,UAAU,KAAK,KAAK;AACxE,QAAI,CAAC,oBAAqB;AAE1B,QAAI,UAAU,KAAK,QAAQ,KAAK,UAAU,KAAK,SAAS,GAAG;AAEzD,YAAM,SAAS,UAAU,KAAK,IAAI,IAAI,KAAK,QAAQ,KAAK;AACxD,UAAI,CAAC,IAAI,WAAW,MAAM,GAAG;AAC3B,YAAI,OAAO,MAAM,oFAAoF;AACrG;AAAA,MACF;AACA,UAAI,OAAO,IAAI;AAAA,IACjB;AAAA,EACF;AACF;AAEA,SAAS,UAAU,MAAwB;AACzC,MAAI,KAAK,SAAY,gBAAW,YAAa,QAAO;AACpD,MAAO,kBAAa,IAAI,KAAK,KAAK,SAAS,YAAa,QAAO;AAE/D,MAAO,sBAAiB,IAAI,EAAG,QAAO;AACtC,SAAO;AACT;;;AC3CA,YAAYC,UAAQ;AAGb,IAAM,YAAoB;AAAA,EAC/B,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,oBAAe,IAAI,EAAG;AAC9B,QAAI,KAAK,KAAK,SAAY,gBAAW,WAAY;AACjD,QAAI,OAAO,IAAI;AAAA,EACjB;AACF;;;ACdA,YAAYC,UAAQ;AAGb,IAAM,0BAAkC;AAAA,EAC7C,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,KAAK,SAAY,gBAAW,WAAY;AAC5C,QAAI,KAAK,UAAa,oBAAe,KAAK,MAAM,EAAG;AACnD,QAAI,OAAO,IAAI;AAAA,EACjB;AACF;;;ACdA,YAAYC,UAAQ;AAGpB,SAAS,wBAAwB,QAAgB,QAAyB;AACxE,MAAI,QAAQ;AACZ,WAAS,IAAI,SAAS,GAAG,KAAK,GAAG,KAAK;AACpC,QAAI,OAAO,CAAC,MAAM,IAAK;AACvB,QAAI,OAAO,CAAC,MAAM,KAAK;AACrB,UAAI,UAAU,GAAG;AACf,cAAM,SAAS,OAAO,MAAM,KAAK,IAAI,GAAG,IAAI,GAAG,GAAG,CAAC,EAAE,QAAQ;AAC7D,YAAI,4BAA4B,KAAK,MAAM,GAAG;AAC5C;AACA;AAAA,QACF;AACA,eAAO;AAAA,MACT;AACA;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,uBAA+B;AAAA,EAC1C,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,uBAAkB,IAAI,EAAG;AAEjC,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,UAAU,CAAI,iBAAY,MAAM,EAAG;AACxC,QAAI,OAAO,SAAS,KAAM;AAE1B,UAAM,SAAS,KAAK,SAAS,IAAI,UAAU;AAC3C,QAAI,CAAC,wBAAwB,IAAI,QAAQ,MAAM,EAAG;AAClD,QAAI,OAAO,IAAI;AAAA,EACjB;AACF;;;ACvCA,YAAYC,UAAQ;AAGb,IAAM,kBAA0B;AAAA,EACrC,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,oBAAe,IAAI,EAAG;AAC9B,QAAI,CAAI,oBAAe,KAAK,UAAU,EAAG;AACzC,QAAI,KAAK,WAAW,KAAK,SAAY,gBAAW,eAAgB;AAChE,QAAI,OAAO,IAAI;AAAA,EACjB;AACF;;;ACfA,YAAYC,UAAQ;AAIb,IAAM,4BAAoC;AAAA,EAC/C,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,wBAAmB,IAAI,EAAG;AAClC,QAAI,KAAK,cAAc,SAAY,gBAAW,wBAAyB;AAEvE,UAAM,OAAO,KAAK;AAClB,UAAM,QAAQ,KAAK;AAGnB,QAAO,kBAAa,IAAI,KAAK,mBAAmB,OAAO,KAAK,IAAI,GAAG;AACjE,UAAI,IAAI,WAAW,IAAI,EAAG;AAC1B,UAAI,OAAO,IAAI;AACf;AAAA,IACF;AAGA,QAAO,wBAAmB,IAAI,KAAK,YAAY,IAAI,GAAG;AACpD,YAAM,UAAU,yBAAyB,IAAI;AAC7C,UAAI,WAAW,mBAAmB,OAAO,OAAO,GAAG;AAGjD,cAAM,YAAe,kBAAa,KAAK,IAAI,IAAI,KAAK,OAAO,KAAK;AAChE,YAAO,kBAAa,SAAS,KAAK,UAAU,SAAS,WAAW,CAAC,IAAI,WAAW,SAAS,GAAG;AAC1F,cAAI,OAAO,IAAI;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAGA,SAAS,mBAAmB,MAAe,MAAuB;AAChE,QAAM,OAAO,kBAAkB,IAAI;AACnC,SAAU,kBAAa,IAAI,KAAK,KAAK,SAAS;AAChD;AAGA,SAAS,kBAAkB,MAAwB;AACjD,MAAO,gCAA2B,IAAI,EAAG,QAAO,kBAAkB,KAAK,UAAU;AACjF,MAAO,+BAA0B,IAAI,EAAG,QAAO,kBAAkB,KAAK,UAAU;AAChF,MAAO,sBAAiB,IAAI,EAAG,QAAO,kBAAkB,KAAK,UAAU;AACvE,SAAO;AACT;AAGA,SAAS,YAAY,MAAoC;AACvD,QAAM,KAAK,KAAK,cAAc;AAC9B,MAAI,OAAU,gBAAW,0BAA0B,OAAU,gBAAW,6BAA8B,QAAO;AAC7G,SAAO,iBAAiB,KAAK,KAAK,KAAK,iBAAiB,KAAK,IAAI;AACnE;AAGA,SAAS,yBAAyB,MAA0C;AAC1E,MAAO,kBAAa,KAAK,IAAI,KAAK,iBAAiB,KAAK,KAAK,EAAG,QAAO,KAAK,KAAK;AACjF,MAAO,kBAAa,KAAK,KAAK,KAAK,iBAAiB,KAAK,IAAI,EAAG,QAAO,KAAK,MAAM;AAClF,SAAO;AACT;;;ACjEA,YAAYC,UAAQ;AAGb,IAAM,0BAAkC;AAAA,EAC7C,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,2BAAsB,IAAI,KAAK,CAAI,qBAAgB,IAAI,EAAG;AAClE,QAAI,CAAC,KAAK,QAAQ,CAAI,aAAQ,KAAK,IAAI,EAAG;AAC1C,UAAM,QAAQ,KAAK,KAAK;AACxB,QAAI,MAAM,WAAW,EAAG;AAExB,UAAM,YAAY,MAAM,CAAC;AACzB,QAAI,cAAc,UAAa,CAAI,2BAAsB,SAAS,EAAG;AACrE,UAAM,OAAO,UAAU;AACvB,QAAI,CAAI,wBAAmB,IAAI,KAAK,KAAK,cAAc,SAAY,gBAAW,YAAa;AAE3F,UAAM,QAAQ,KAAK;AACnB,QAAI,CAAI,wBAAmB,KAAK,KAAK,MAAM,cAAc,SAAY,gBAAW,sBAAuB;AAEvG,QAAI,CAAI,kBAAa,KAAK,IAAI,KAAK,CAAI,kBAAa,MAAM,IAAI,EAAG;AACjE,QAAI,KAAK,KAAK,SAAS,MAAM,KAAK,KAAM;AAExC,UAAM,YAAY,KAAK,KAAK;AAC5B,UAAM,UAAU,KAAK,WAAW,KAAK,CAAC,MAAS,kBAAa,EAAE,IAAI,KAAK,EAAE,KAAK,SAAS,SAAS;AAChG,QAAI,QAAS,KAAI,OAAO,SAAS;AAAA,EACnC;AACF;;;AC9BA,YAAYC,UAAQ;AAGb,IAAM,+BAAuC;AAAA,EAClD,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,2BAAsB,IAAI,KAAK,CAAI,qBAAgB,IAAI,EAAG;AAClE,QAAI,CAAC,KAAK,QAAQ,CAAI,aAAQ,KAAK,IAAI,EAAG;AAC1C,UAAM,QAAQ,KAAK,KAAK;AACxB,QAAI,MAAM,WAAW,EAAG;AAExB,UAAM,YAAY,MAAM,CAAC;AACzB,QAAI,cAAc,UAAa,CAAI,mBAAc,SAAS,EAAG;AAE7D,UAAM,OAAO,UAAU;AACvB,QAAI,cAA6B;AAGjC,QAAO,6BAAwB,IAAI,KAAK,KAAK,aAAgB,gBAAW,kBAAkB;AACxF,UAAO,kBAAa,KAAK,OAAO,EAAG,eAAc,KAAK,QAAQ;AAAA,IAChE;AAEA,QAAO,wBAAmB,IAAI,GAAG;AAC/B,YAAM,KAAK,KAAK,cAAc;AAC9B,UAAI,OAAU,gBAAW,2BAA2B,OAAU,gBAAW,mBAAmB;AAC1F,YAAO,kBAAa,KAAK,IAAI,KAAQ,kBAAa,KAAK,KAAK,KAAK,KAAK,MAAM,SAAS,aAAa;AAChG,wBAAc,KAAK,KAAK;AAAA,QAC1B;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,YAAa;AAElB,UAAM,aAAa,UAAU;AAC7B,UAAM,UACD,uBAAkB,UAAU,KAC5B,sBAAiB,UAAU,KAC1B,aAAQ,UAAU,KACpB,WAAW,WAAW,WAAW,KACjC,WAAW,WAAW,CAAC,MAAM,WACzB,uBAAkB,WAAW,WAAW,CAAC,CAAC,KAAQ,sBAAiB,WAAW,WAAW,CAAC,CAAC;AAEnG,QAAI,CAAC,QAAS;AAEd,UAAM,aAAa,KAAK,WAAW;AAAA,MACjC,CAAC,MAAS,kBAAa,EAAE,IAAI,KAAK,EAAE,KAAK,SAAS,eAAe,EAAE,kBAAkB;AAAA,IACvF;AACA,QAAI,WAAY,KAAI,OAAO,SAAS;AAAA,EACtC;AACF;;;ACnDO,IAAM,2BAA0C;AAAA,EACrD,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,QAAQ,SAAqC;AAC3C,UAAM,cAA4B,CAAC;AACnC,eAAW,SAAS,QAAQ,MAAM,mBAAmB,GAAG;AAEtD,YAAM,QAAQ,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAC9C,UAAI,MAAM,OAAO,EAAG;AAEpB,YAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE,IAAI;AACxF,iBAAW,SAAS,OAAO,MAAM,CAAC,GAAG;AACnC,cAAM,SAAS,OACZ,OAAO,CAAC,MAAM,MAAM,KAAK,EACzB,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,IAAI,IAAI,EAAE,IAAI,GAAG,EAC5C,KAAK,IAAI;AACZ,oBAAY,KAAK;AAAA,UACf,QAAQ,KAAK;AAAA,UACb,UAAU,KAAK;AAAA,UACf,SAAS,SAAS,MAAM,IAAI,6BAA6B,MAAM;AAAA,UAC/D,MAAM,MAAM;AAAA,UACZ,MAAM,MAAM;AAAA,UACZ,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;;;AChCA,YAAYC,UAAQ;AAGb,IAAM,+BAA8C;AAAA,EACzD,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,QAAQ,SAAqC;AAC3C,UAAM,cAA4B,CAAC;AACnC,eAAW,SAAS,QAAQ,UAAU,mBAAmB,GAAG;AAC1D,YAAM,QAAQ,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAC9C,UAAI,MAAM,OAAO,EAAG;AAIpB,YAAM,QAAQ,MAAM,CAAC;AACrB,UAAI,UAAU,UAAa,SAAS,MAAM,IAAI,EAAG;AAEjD,YAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE,IAAI;AACxF,iBAAW,SAAS,OAAO,MAAM,CAAC,GAAG;AACnC,cAAM,SAAS,OACZ,OAAO,CAAC,MAAM,MAAM,KAAK,EACzB,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,KAAK,EAAE,IAAI,IAAI,EAAE,IAAI,GAAG,EAC5C,KAAK,IAAI;AACZ,oBAAY,KAAK;AAAA,UACf,QAAQ,KAAK;AAAA,UACb,UAAU,KAAK;AAAA,UACf,SAAS,aAAa,MAAM,IAAI,4BAA4B,MAAM;AAAA,UAClE,MAAM,MAAM;AAAA,UACZ,MAAM,MAAM;AAAA,UACZ,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,SAAS,MAAwB;AACxC,MAAI;AACJ,MAAO,2BAAsB,IAAI,KAAQ,0BAAqB,IAAI,GAAG;AACnE,WAAO,KAAK;AAAA,EACd,WAAc,qBAAgB,IAAI,GAAG;AACnC,WAAU,aAAQ,KAAK,IAAI,IAAI,KAAK,OAAO;AAAA,EAC7C;AACA,MAAI,CAAC,QAAQ,KAAK,WAAW,WAAW,EAAG,QAAO;AAClD,QAAM,OAAO,KAAK,WAAW,CAAC;AAC9B,MAAI,SAAS,UAAa,CAAI,2BAAsB,IAAI,EAAG,QAAO;AAClE,SAAU,wBAAmB,KAAK,UAAU,KAC1C,KAAK,WAAW,cAAc,SAAY,gBAAW;AACzD;;;ACjDO,IAAM,wBAAuC;AAAA,EAClD,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,QAAQ,SAAqC;AAC3C,UAAM,cAA4B,CAAC;AAEnC,eAAW,MAAM,QAAQ,UAAU,OAAO,GAAG;AAE3C,eAAS,IAAI,GAAG,IAAI,GAAG,OAAO,QAAQ,KAAK;AACzC,cAAM,QAAQ,GAAG,OAAO,CAAC;AACzB,YAAI,UAAU,OAAW;AACzB,YAAI,CAAC,MAAM,YAAY,CAAC,MAAM,WAAY;AAG1C,cAAM,YAAY,GAAG,SACjB,QAAQ,UAAU,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,IACtD,QAAQ,UAAU,OAAO,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;AAG5D,YAAI,UAAU,SAAS,EAAG;AAG1B,cAAM,aAAa,UAAU,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC;AACxD,YAAI,YAAY;AACd,sBAAY,KAAK;AAAA,YACf,QAAQ,KAAK;AAAA,YACb,UAAU,KAAK;AAAA,YACf,SAAS,uBAAuB,MAAM,IAAI,+BAA+B,UAAU,MAAM;AAAA,YACzF,MAAM,GAAG;AAAA,YACT,MAAM,GAAG;AAAA,YACT,QAAQ;AAAA,UACV,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;;;AC1CA,YAAYC,UAAQ;AAGb,IAAM,gBAAwB;AAAA,EACnC,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SACE;AAAA,EAEF,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,mBAAc,IAAI,EAAG;AAC7B,UAAM,QAAQ,KAAK;AACnB,QAAI,UAAU,KAAK,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,WAAW,KAAK,GAAG;AAC9D,UAAI,OAAO,IAAI;AAAA,IACjB;AAAA,EACF;AACF;AAEA,SAAS,UAAU,OAAiB,WAAgD;AAClF,WAAS,MAAM,MAAwB;AACrC,QAAI,UAAU,IAAI,EAAG,QAAO;AAC5B,QAAO,2BAAsB,IAAI,KAAQ,0BAAqB,IAAI,KAAQ,qBAAgB,IAAI,EAAG,QAAO;AACxG,WAAU,kBAAa,MAAM,KAAK,KAAK;AAAA,EACzC;AACA,SAAU,kBAAa,OAAO,KAAK,KAAK;AAC1C;AAEA,SAAS,UAAU,OAA0B;AAC3C,SAAO,UAAU,OAAO,CAAC,MAAS,uBAAkB,CAAC,CAAC;AACxD;AAEA,SAAS,SAAS,OAA0B;AAC1C,SAAO,UAAU,OAAO,CAAC,MAAS,sBAAiB,CAAC,CAAC;AACvD;AAEA,IAAM,cAAc,oBAAI,IAAI,CAAC,WAAW,UAAU,KAAK,CAAC;AAExD,SAAS,WAAW,OAA0B;AAC5C,SAAO,UAAU,OAAO,CAAC,MAAM;AAC7B,QAAI,CAAI,sBAAiB,CAAC,EAAG,QAAO;AACpC,UAAM,SAAS,EAAE;AACjB,QAAI,CAAI,gCAA2B,MAAM,EAAG,QAAO;AACnD,QAAI,CAAI,kBAAa,OAAO,UAAU,EAAG,QAAO;AAChD,WAAO,YAAY,IAAI,OAAO,WAAW,IAAI;AAAA,EAC/C,CAAC;AACH;;;AC9CA,YAAYC,UAAQ;AAGb,IAAM,gBAAwB;AAAA,EACnC,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SACE;AAAA,EAEF,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,mBAAc,IAAI,EAAG;AAC7B,QAAI,CAAC,KAAK,oBAAqB;AAC/B,UAAM,QAAQ,KAAK,oBAAoB;AACvC,QAAI,CAAI,kBAAa,KAAK,EAAG;AAC7B,UAAM,YAAY,MAAM;AACxB,gBAAY,KAAK,OAAO,WAAW,GAAG;AAAA,EACxC;AACF;AAEA,SAAS,YAAY,OAAiB,WAAmB,KAA2B;AAClF,WAAS,MAAM,MAAqB;AAClC,QAAO,2BAAsB,IAAI,KAAQ,0BAAqB,IAAI,KAAQ,qBAAgB,IAAI,EAAG;AACjG,QAAO,sBAAiB,IAAI,KAAK,KAAK,cAAiB,qBAAgB,KAAK,UAAU,GAAG;AACvF,YAAM,OAAO,KAAK,WAAW;AAC7B,UAAI,QAAQ,KAAK,SAAS,KAAK,eAAe,MAAM,SAAS,KAAK,CAAC,YAAY,IAAI,GAAG;AACpF,YAAI,OAAO,IAAI;AAAA,MACjB;AACA;AAAA,IACF;AACA,IAAG,kBAAa,MAAM,KAAK;AAAA,EAC7B;AACA,EAAG,kBAAa,OAAO,KAAK;AAC9B;AAEA,SAAS,eAAe,MAAgC,MAAuB;AAC7E,aAAW,OAAO,MAAM;AACtB,QAAI,mBAAmB,KAAK,IAAI,EAAG,QAAO;AAAA,EAC5C;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,MAAe,MAAuB;AAChE,MAAO,kBAAa,IAAI,KAAK,KAAK,SAAS,KAAM,QAAO;AACxD,SAAU,kBAAa,MAAM,CAAC,UAAU,mBAAmB,OAAO,IAAI,KAAK,MAAS,KAAK;AAC3F;AAEA,SAAS,YAAY,MAAyC;AAC5D,aAAW,OAAO,MAAM;AACtB,QAAO,+BAA0B,GAAG,GAAG;AACrC,iBAAW,QAAQ,IAAI,YAAY;AACjC,YAAO,0BAAqB,IAAI,KAAQ,kBAAa,KAAK,IAAI,KAAK,KAAK,KAAK,SAAS,QAAS,QAAO;AACtG,YAAO,mCAA8B,IAAI,KAAK,KAAK,KAAK,SAAS,QAAS,QAAO;AAAA,MACnF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;ACzDA,YAAYC,UAAQ;AAIb,IAAM,kBAAiC;AAAA,EAC5C,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,QAAQ,SAAqC;AAC3C,UAAM,cAA4B,CAAC;AAGnC,UAAM,iBAAiB,oBAAI,IAAY;AACvC,UAAM,mBAAmB,oBAAI,IAAe;AAC5C,eAAW,MAAM,QAAQ,UAAU,OAAO,GAAG;AAC3C,qBAAe,IAAI,GAAG,IAAI;AAC1B,UAAI,GAAG,OAAQ,kBAAiB,IAAI,GAAG,MAAM;AAAA,IAC/C;AAEA,eAAW,QAAQ,QAAQ,WAAW;AAEpC,YAAM,cAAc,KAAK,SACrB,iBAAiB,IAAI,KAAK,MAAM,IAChC,eAAe,IAAI,KAAK,UAAU;AACtC,UAAI,CAAC,YAAa;AAElB,eAAS,IAAI,GAAG,IAAI,KAAK,KAAK,UAAU,QAAQ,KAAK;AACnD,cAAM,MAAM,KAAK,KAAK,UAAU,CAAC;AACjC,YAAI,QAAQ,OAAW;AACvB,YAAI,iBAAiB,GAAG,GAAG;AACzB,gBAAM,MAAM,IAAI,SAAY,gBAAW,cAAc,SAAS;AAC9D,sBAAY,KAAK;AAAA,YACf,QAAQ,KAAK;AAAA,YACb,UAAU,KAAK;AAAA,YACf,SAAS,oBAAoB,GAAG,QAAQ,KAAK,UAAU,iBAAiB,IAAI,CAAC;AAAA,YAC7E,MAAM,KAAK;AAAA,YACX,MAAM,KAAK;AAAA,YACX,QAAQ;AAAA,UACV,CAAC;AACD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;;;AC/CA,SAAS,SAAS,eAAe;AAGjC,IAAM,aAAa,CAAC,OAAO,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,QAAQ,MAAM;AAEzE,IAAM,wBAAuC;AAAA,EAClD,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,QAAQ,SAAqC;AAC3C,UAAM,cAA4B,CAAC;AACnC,eAAW,SAAS,QAAQ,UAAU,uBAAuB,GAAG;AAE9D,YAAM,SAAS,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAC/C,UAAI,OAAO,SAAS,EAAG;AAIvB,YAAM,aAAa,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACnD,YAAM,QAAQ,MAAM,CAAC;AACrB,UAAI,UAAU,OAAW;AACzB,YAAM,WAAW,MAAM;AACvB,YAAM,gBAAgB,QAAQ,QAAQ,KAAK,CAAC,QAAQ;AAClD,YAAI,CAAC,WAAW,IAAI,IAAI,IAAI,EAAG,QAAO;AACtC,YAAI,IAAI,iBAAiB,YAAY,IAAI,cAAc,SAAU,QAAO;AACxE,YAAI,CAAC,IAAI,OAAO,WAAW,GAAG,EAAG,QAAO;AACxC,cAAM,aAAa,kBAAkB,IAAI,MAAM,IAAI,MAAM;AAEzD,YAAI,WAAW,KAAK,CAAC,MAAM,WAAW,IAAI,CAAC,CAAC,EAAG,QAAO;AAEtD,eAAO,WAAW;AAAA,UAAK,CAAC,MACtB,QAAQ,QAAQ,KAAK,CAAC,UAAU;AAC9B,gBAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,gBAAI,MAAM,iBAAiB,YAAY,MAAM,cAAc,SAAU,QAAO;AAC5E,gBAAI,CAAC,MAAM,OAAO,WAAW,GAAG,EAAG,QAAO;AAC1C,kBAAM,kBAAkB,kBAAkB,MAAM,MAAM,MAAM,MAAM;AAClE,mBAAO,gBAAgB,KAAK,CAAC,OAAO,WAAW,IAAI,EAAE,CAAC;AAAA,UACxD,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AACD,UAAI,cAAe;AAEnB,YAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE,IAAI;AACxF,iBAAW,SAAS,OAAO,MAAM,CAAC,GAAG;AACnC,cAAM,SAAS,OACZ,OAAO,CAAC,MAAM,MAAM,KAAK,EACzB,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,IAAI,EAAE,EAChC,KAAK,IAAI;AACZ,oBAAY,KAAK;AAAA,UACf,QAAQ,KAAK;AAAA,UACb,UAAU,KAAK;AAAA,UACf,SAAS,sBAAsB,MAAM,IAAI,sBAAsB,MAAM;AAAA,UACrE,MAAM,MAAM;AAAA,UACZ,MAAM,MAAM;AAAA,UACZ,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,kBAAkB,UAAkB,WAA6B;AACxE,QAAM,OAAO,QAAQ,QAAQ,QAAQ,GAAG,SAAS;AACjD,QAAM,aAAa,CAAC,IAAI;AACxB,aAAW,OAAO,YAAY;AAC5B,eAAW,KAAK,OAAO,GAAG;AAC1B,eAAW,KAAK,QAAQ,MAAM,UAAU,GAAG,CAAC;AAAA,EAC9C;AACA,SAAO;AACT;;;ACvEA,YAAYC,UAAQ;AAGb,IAAM,oBAAmC;AAAA,EAC9C,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,QAAQ,SAAqC;AAC3C,UAAM,cAA4B,CAAC;AACnC,eAAW,SAAS,QAAQ,MAAM,uBAAuB,GAAG;AAE1D,YAAM,SAAS,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAC/C,UAAI,OAAO,SAAS,EAAG;AAIvB,YAAM,kBAAkB,MAAM;AAAA,QAC5B,CAAC,MAAM,CAAI,uBAAkB,EAAE,IAAI,KAAK,CAAI,4BAAuB,EAAE,IAAI;AAAA,MAC3E;AACA,UAAI,gBAAiB;AAErB,YAAM,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,KAAK,EAAE,OAAO,EAAE,IAAI;AACxF,iBAAW,SAAS,OAAO,MAAM,CAAC,GAAG;AACnC,cAAM,SAAS,OACZ,OAAO,CAAC,MAAM,MAAM,KAAK,EACzB,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,IAAI,EAAE,EAChC,KAAK,IAAI;AACZ,oBAAY,KAAK;AAAA,UACf,QAAQ,KAAK;AAAA,UACb,UAAU,KAAK;AAAA,UACf,SAAS,kBAAkB,MAAM,IAAI,sBAAsB,MAAM;AAAA,UACjE,MAAM,MAAM;AAAA,UACZ,MAAM,MAAM;AAAA,UACZ,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACF;;;ACxCA,YAAYC,UAAQ;AAGb,IAAM,kBAA0B;AAAA,EACrC,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,UAAU;AAAA,EACV,SAAS;AAAA,EAET,MAAM,MAAe,KAAqB;AACxC,QAAI,CAAI,sBAAiB,IAAI,EAAG;AAChC,QAAI,KAAK,WAAW,SAAY,gBAAW,cAAe;AAC1D,QAAI,OAAO,IAAI;AAAA,EACjB;AACF;;;ACeO,IAAM,WAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;;;ACxDA,OAAO,QAAQ;AACf,SAAS,gBAAAC,qBAAoB;;;AC0CtB,SAAS,SAAS,GAAsB;AAC7C,SAAO,UAAU,KAAK,EAAE,SAAS;AACnC;;;AC7CA,YAAYC,UAAQ;AACpB,OAA6B;;;ACD7B,SAAS,kBAAkB;AAC3B,YAAYC,UAAQ;AAMb,SAAS,cAAc,MAAe,YAAmC;AAC9E,QAAM,aAAa,kBAAkB,MAAM,UAAU;AACrD,SAAO,WAAW,QAAQ,EAAE,OAAO,UAAU,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAC1E;AAEA,SAAS,kBAAkB,MAAe,YAAmC;AAC3E,MAAO,uBAAkB,IAAI,GAAG;AAC9B,UAAM,aAAa,KAAK,QAAQ,IAAI,CAAC,MAAM,kBAAkB,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG;AAC5F,WAAO,IAAI,UAAU;AAAA,EACvB;AACA,MAAO,4BAAuB,IAAI,GAAG;AACnC,UAAM,aAAa,KAAK,QAAQ,IAAI,CAAC,MAAM,kBAAkB,GAAG,UAAU,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG;AAC5F,WAAO,IAAI,UAAU;AAAA,EACvB;AACA,MAAO,yBAAoB,IAAI,GAAG;AAChC,UAAM,UAAU,KAAK,KAAK,QAAQ,UAAU;AAC5C,UAAM,WAAW,KAAK,gBAAgB,MAAM;AAC5C,UAAM,OAAO,KAAK,OAAO,kBAAkB,KAAK,MAAM,UAAU,IAAI;AACpE,WAAO,GAAG,OAAO,GAAG,QAAQ,IAAI,IAAI;AAAA,EACtC;AACA,MAAO,4BAAuB,IAAI,GAAG;AACnC,WAAO,kBAAkB,KAAK,MAAM,UAAU;AAAA,EAChD;AAEA,SAAO,KAAK,QAAQ,UAAU,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAC5D;AAMO,SAAS,iBAAiB,MAAe,YAAmC;AACjF,QAAM,WAAW,KAAK,QAAQ,UAAU;AACxC,QAAM,aAAa,SAAS,QAAQ,QAAQ,GAAG,EAAE,KAAK;AACtD,SAAO,WAAW,QAAQ,EAAE,OAAO,UAAU,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAC1E;;;AC9BO,IAAM,eAAN,MAAmB;AAAA,EAChB,UAAuB,CAAC;AAAA,EACxB,SAAS,oBAAI,IAAyB;AAAA,EAE9C,IAAI,MAAc,MAAc,MAAc,UAAmB,YAA2B,UAAyB;AACnH,UAAM,OAAO,cAAc,UAAU,UAAU;AAC/C,UAAM,QAAmB,EAAE,MAAM,MAAM,MAAM,MAAM,MAAM,UAAU,SAAS;AAC5E,SAAK,QAAQ,KAAK,KAAK;AACvB,QAAI,OAAO,KAAK,OAAO,IAAI,IAAI;AAC/B,QAAI,SAAS,QAAW;AACtB,aAAO,CAAC;AACR,WAAK,OAAO,IAAI,MAAM,IAAI;AAAA,IAC5B;AACA,SAAK,KAAK,KAAK;AAAA,EACjB;AAAA,EAEA,qBAAoC;AAClC,WAAO,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC,EAAE,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC;AAAA,EACrE;AAAA,EAEA,SAAsB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,yBAAwC;AACtC,UAAM,SAAS,oBAAI,IAAyB;AAC5C,eAAW,SAAS,KAAK,SAAS;AAChC,UAAI,CAAC,MAAM,SAAU;AACrB,UAAI,OAAO,OAAO,IAAI,MAAM,IAAI;AAChC,UAAI,SAAS,QAAW;AACtB,eAAO,CAAC;AACR,eAAO,IAAI,MAAM,MAAM,IAAI;AAAA,MAC7B;AACA,WAAK,KAAK,KAAK;AAAA,IACjB;AACA,WAAO,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,OAAO,CAAC,UAAU;AAC5C,UAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,YAAM,QAAQ,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAC9C,aAAO,MAAM,OAAO;AAAA,IACtB,CAAC;AAAA,EACH;AACF;;;ACjCO,IAAM,mBAAN,MAAuB;AAAA,EACpB,UAA2B,CAAC;AAAA,EAC5B,SAAS,oBAAI,IAA6B;AAAA,EAElD,IAAI,OAA4B;AAC9B,SAAK,QAAQ,KAAK,KAAK;AACvB,QAAI,OAAO,KAAK,OAAO,IAAI,MAAM,IAAI;AACrC,QAAI,SAAS,QAAW;AACtB,aAAO,CAAC;AACR,WAAK,OAAO,IAAI,MAAM,MAAM,IAAI;AAAA,IAClC;AACA,SAAK,KAAK,KAAK;AAAA,EACjB;AAAA,EAEA,qBAAwC;AACtC,WAAO,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC,EAAE,OAAO,CAAC,UAAU,MAAM,SAAS,CAAC;AAAA,EACrE;AAAA,EAEA,SAA0B;AACxB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAU,MAA+B;AACvC,WAAO,KAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,IAAI;AAAA,EACnD;AAAA,EAEA,yBAA4C;AAC1C,UAAM,SAAS,oBAAI,IAA6B;AAChD,eAAW,SAAS,KAAK,SAAS;AAChC,UAAI,CAAC,MAAM,SAAU;AACrB,UAAI,OAAO,OAAO,IAAI,MAAM,IAAI;AAChC,UAAI,SAAS,QAAW;AACtB,eAAO,CAAC;AACR,eAAO,IAAI,MAAM,MAAM,IAAI;AAAA,MAC7B;AACA,WAAK,KAAK,KAAK;AAAA,IACjB;AACA,WAAO,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE,OAAO,CAAC,UAAU;AAC5C,UAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,YAAM,QAAQ,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AAC9C,aAAO,MAAM,OAAO;AAAA,IACtB,CAAC;AAAA,EACH;AACF;;;AH1BO,SAAS,eAAe,SAAmC;AAChE,QAAM,UAAU,QAAQ,eAAe;AACvC,QAAM,QAAQ,IAAI,aAAa;AAC/B,QAAM,YAAY,IAAI,iBAAiB;AACvC,QAAM,YAAwB,CAAC;AAC/B,QAAM,UAAyB,CAAC;AAChC,QAAM,UAAU,oBAAI,IAAoF;AAExG,aAAW,cAAc,QAAQ,eAAe,GAAG;AAUjD,QAASC,SAAT,SAAe,MAAqB;AAClC,mBAAa,MAAM,MAAM,YAAY,KAAK;AAC1C,uBAAiB,MAAM,MAAM,YAAY,SAAS,SAAS;AAC3D,uBAAiB,MAAM,MAAM,YAAY,SAAS,SAAS;AAC3D,qBAAe,MAAM,MAAM,OAAO;AAClC,MAAG,kBAAa,MAAMA,MAAK;AAAA,IAC7B;AANS,gBAAAA;AATT,UAAM,OAAO,WAAW;AAExB,QAAI,WAAW,kBAAmB;AAClC,QAAI,KAAK,SAAS,cAAc,EAAG;AAEnC,UAAM,SAAS,WAAW,YAAY;AACtC,UAAM,WAAW,mBAAmB,UAAU;AAC9C,YAAQ,IAAI,MAAM,EAAE,QAAQ,YAAY,SAAS,CAAC;AASlD,IAAG,kBAAa,YAAYA,MAAK;AAAA,EACnC;AAEA,SAAO,EAAE,OAAO,WAAW,WAAW,SAAS,OAAO,QAAQ;AAChE;AAEA,SAAS,WAAW,MAAwB;AAC1C,MAAI,CAAI,sBAAiB,IAAI,EAAG,QAAO;AACvC,QAAM,OAAU,kBAAa,IAAI;AACjC,MAAI,SAAS,OAAW,QAAO;AAC/B,SAAO,KAAK,KAAK,CAAC,MAAM,EAAE,SAAY,gBAAW,aAAa;AAChE;AAEA,SAAS,aAAa,MAAe,MAAc,YAA2B,UAA8B;AAC1G,MAAO,4BAAuB,IAAI,GAAG;AACnC,UAAM,OAAU,mCAA8B,YAAY,KAAK,SAAS,UAAU,CAAC,EAAE,OAAO;AAC5F,aAAS,IAAI,KAAK,KAAK,MAAM,MAAM,MAAM,KAAK,MAAM,YAAY,WAAW,IAAI,CAAC;AAAA,EAClF;AACA,MAAO,4BAAuB,IAAI,GAAG;AACnC,UAAM,OAAU,mCAA8B,YAAY,KAAK,SAAS,UAAU,CAAC,EAAE,OAAO;AAC5F,aAAS,IAAI,KAAK,KAAK,MAAM,MAAM,MAAM,MAAM,YAAY,WAAW,IAAI,CAAC;AAAA,EAC7E;AACF;AAEA,SAAS,iBAAiB,MAAe,MAAc,YAA2B,SAAyB,UAAkC;AAC3I,MAAO,2BAAsB,IAAI,KAAK,KAAK,QAAQ,KAAK,MAAM;AAC5D,UAAM,OAAU,mCAA8B,YAAY,KAAK,SAAS,UAAU,CAAC,EAAE,OAAO;AAC5F,UAAM,SAAS,cAAc,KAAK,YAAY,UAAU;AACxD,UAAM,OAAO,iBAAiB,KAAK,MAAM,UAAU;AACnD,UAAM,SAAS,QAAQ,oBAAoB,KAAK,IAAI;AACpD,aAAS,IAAI,EAAE,MAAM,KAAK,KAAK,MAAM,MAAM,MAAM,MAAM,QAAQ,MAAM,UAAU,WAAW,IAAI,GAAG,OAAO,CAAC;AAAA,EAC3G;AAGA,MAAO,yBAAoB,IAAI,GAAG;AAChC,UAAM,WAAW,WAAW,IAAI;AAChC,eAAW,QAAQ,KAAK,gBAAgB,cAAc;AACpD,UAAI,KAAK,eAAkB,qBAAgB,KAAK,WAAW,KAAQ,kBAAa,KAAK,IAAI,GAAG;AAC1F,cAAM,QAAQ,KAAK;AACnB,cAAM,OAAU,aAAQ,MAAM,IAAI,IAAI,MAAM,OAAO,MAAM;AACzD,cAAM,OAAU,mCAA8B,YAAY,KAAK,SAAS,UAAU,CAAC,EAAE,OAAO;AAC5F,cAAM,SAAS,cAAc,MAAM,YAAY,UAAU;AACzD,cAAM,OAAO,iBAAiB,MAAM,UAAU;AAC9C,cAAM,SAAS,QAAQ,oBAAoB,KAAK,IAAI;AACpD,iBAAS,IAAI,EAAE,MAAM,KAAK,KAAK,MAAM,MAAM,MAAM,MAAM,QAAQ,MAAM,OAAO,UAAU,OAAO,CAAC;AAAA,MAChG;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,cAAc,YAAmD,YAAwC;AAChH,SAAO,WAAW,IAAI,CAAC,MAAM;AAC3B,UAAM,OAAO,EAAE,KAAK,QAAQ,UAAU;AACtC,UAAM,WAAW,EAAE,kBAAkB;AACrC,UAAM,aAAa,EAAE,gBAAgB;AACrC,UAAM,WAAW,EAAE,OAAO,EAAE,KAAK,QAAQ,UAAU,IAAI;AACvD,WAAO,EAAE,MAAM,UAAU,YAAY,SAAS;AAAA,EAChD,CAAC;AACH;AAEA,SAAS,eAAe,MAAe,MAAc,SAA8B;AACjF,MAAO,yBAAoB,IAAI,KAAQ,qBAAgB,KAAK,eAAe,GAAG;AAC5E,UAAM,eAAe,KAAK,gBAAgB;AAC1C,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,OAAQ;AAGb,QAAI,OAAO,MAAM;AACf,cAAQ,KAAK;AAAA,QACX;AAAA,QACA,WAAW,OAAO,KAAK;AAAA,QACvB,cAAc;AAAA,QACd,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAGA,QAAI,OAAO,iBAAoB,oBAAe,OAAO,aAAa,GAAG;AACnE,iBAAW,MAAM,OAAO,cAAc,UAAU;AAC9C,gBAAQ,KAAK;AAAA,UACX;AAAA,UACA,WAAW,GAAG,KAAK;AAAA,UACnB,cAAc,GAAG,eAAe,GAAG,aAAa,OAAO,GAAG,KAAK;AAAA,UAC/D,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAGA,MAAO,yBAAoB,IAAI,KAAK,KAAK,mBAAsB,qBAAgB,KAAK,eAAe,GAAG;AACpG,UAAM,eAAe,KAAK,gBAAgB;AAC1C,QAAI,KAAK,gBAAmB,oBAAe,KAAK,YAAY,GAAG;AAC7D,iBAAW,MAAM,KAAK,aAAa,UAAU;AAC3C,gBAAQ,KAAK;AAAA,UACX;AAAA,UACA,WAAW,GAAG,KAAK;AAAA,UACnB,cAAc,GAAG,eAAe,GAAG,aAAa,OAAO,GAAG,KAAK;AAAA,UAC/D,QAAQ;AAAA,QACV,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,MAAe,MAAc,YAA2B,SAAyB,OAAyB;AAClI,MAAI,CAAI,sBAAiB,IAAI,EAAG;AAChC,MAAI,aAA4B;AAChC,MAAO,kBAAa,KAAK,UAAU,GAAG;AACpC,iBAAa,KAAK,WAAW;AAAA,EAC/B,WAAc,gCAA2B,KAAK,UAAU,GAAG;AACzD,iBAAa,KAAK,WAAW,KAAK;AAAA,EACpC;AACA,MAAI,YAAY;AACd,UAAM,OAAU,mCAA8B,YAAY,KAAK,SAAS,UAAU,CAAC,EAAE,OAAO;AAE5F,QAAI;AACJ,QAAI;AACF,eAAS,QAAQ,oBAAoB,KAAK,UAAU;AACpD,UAAI,UAAW,OAAO,QAAW,iBAAY,OAAQ;AACnD,iBAAS,QAAQ,iBAAiB,MAAM;AAAA,MAC1C;AAAA,IACF,QAAQ;AAAA,IAER;AACA,UAAM,KAAK;AAAA,MACT;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,KAAK,UAAU;AAAA,MACzB;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAGO,SAAS,mBAAmB,YAA0C;AAC3E,QAAM,WAA0B,CAAC;AACjC,QAAM,SAAS,WAAW,YAAY;AACtC,QAAM,OAAO,oBAAI,IAAY;AAE7B,WAAS,MAAM,MAAqB;AAClC,UAAM,UAAa,6BAAwB,QAAQ,KAAK,aAAa,CAAC;AACtE,QAAI,SAAS;AACX,iBAAW,KAAK,SAAS;AACvB,YAAI,KAAK,IAAI,EAAE,GAAG,EAAG;AACrB,aAAK,IAAI,EAAE,GAAG;AACd,cAAM,SAAS,EAAE,SAAY,gBAAW;AACxC,iBAAS,KAAK;AAAA,UACZ,MAAM,SAAS,SAAS;AAAA,UACxB,OAAO,OAAO,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC;AAAA,UACzD,OAAO,EAAE;AAAA,UACT,KAAK,EAAE;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,WAAc,8BAAyB,QAAQ,KAAK,OAAO,CAAC;AAClE,QAAI,UAAU;AACZ,iBAAW,KAAK,UAAU;AACxB,YAAI,KAAK,IAAI,EAAE,GAAG,EAAG;AACrB,aAAK,IAAI,EAAE,GAAG;AACd,cAAM,SAAS,EAAE,SAAY,gBAAW;AACxC,iBAAS,KAAK;AAAA,UACZ,MAAM,SAAS,SAAS;AAAA,UACxB,OAAO,OAAO,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC;AAAA,UACzD,OAAO,EAAE;AAAA,UACT,KAAK,EAAE;AAAA,QACT,CAAC;AAAA,MACH;AAAA,IACF;AACA,IAAG,kBAAa,MAAM,KAAK;AAAA,EAC7B;AACA,QAAM,UAAU;AAEhB,SAAO;AACT;;;AI9OA,YAAYC,UAAQ;AACpB,SAAS,WAAAC,gBAAe;AAEjB,SAAS,uBAAuB,OAA6B;AAClE,MAAI;AACJ,MAAI,MAAM,SAAS,GAAG;AACpB,iBAAgB,oBAAeA,SAAQ,MAAM,CAAC,CAAW,GAAM,SAAI,YAAY,eAAe;AAAA,EAChG;AAEA,MAAI,YAAY;AACd,UAAM,aAAgB,oBAAe,YAAe,SAAI,QAAQ;AAChE,UAAM,SAAY,gCAA2B,WAAW,QAAW,UAAKA,SAAQ,UAAU,CAAC;AAC3F,WAAU,mBAAc;AAAA,MACtB,WAAW;AAAA,MACX,SAAS,EAAE,GAAG,OAAO,SAAS,cAAc,KAAK;AAAA,IACnD,CAAC;AAAA,EACH;AAEA,SAAU,mBAAc;AAAA,IACtB,WAAW;AAAA,IACX,SAAS;AAAA,MACP,QAAW,kBAAa;AAAA,MACxB,QAAW,gBAAW;AAAA,MACtB,kBAAqB,0BAAqB;AAAA,MAC1C,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,QAAQ;AAAA,IACV;AAAA,EACF,CAAC;AACH;;;AC7BA,YAAYC,UAAQ;AAIb,SAAS,WACd,OACA,YACA,SACA,QACA,UACc;AACd,QAAM,cAA4B,CAAC;AAEnC,QAAM,WAAW,MAAM,IAAI,CAAC,UAAU;AAAA,IACpC;AAAA,IACA,KAAK,aAAa,MAAM,YAAY,SAAS,QAAQ,UAAU,WAAW;AAAA,EAC5E,EAAE;AAEF,WAAS,MAAM,MAAqB;AAClC,eAAW,EAAE,MAAM,IAAI,KAAK,UAAU;AACpC,WAAK,MAAM,MAAM,GAAG;AAAA,IACtB;AACA,IAAG,kBAAa,MAAM,KAAK;AAAA,EAC7B;AAEA,QAAM,UAAU;AAChB,SAAO;AACT;AAEA,SAAS,aACP,MACA,YACA,SACA,QACA,UACA,aACgB;AAChB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IAEA,OAAO,MAAe,SAAkB;AACtC,YAAM,EAAE,MAAM,UAAU,IAAO,mCAA8B,YAAY,KAAK,SAAS,UAAU,CAAC;AAClG,kBAAY,KAAK;AAAA,QACf,QAAQ,KAAK;AAAA,QACb,UAAU,KAAK;AAAA,QACf,SAAS,WAAW,KAAK;AAAA,QACzB,MAAM;AAAA,QACN,MAAM,OAAO;AAAA,QACb,QAAQ,YAAY;AAAA,MACtB,CAAC;AAAA,IACH;AAAA,IAEA,eAAe,QAAgB,SAAkB;AAC/C,YAAM,EAAE,MAAM,UAAU,IAAO,mCAA8B,YAAY,MAAM;AAC/E,kBAAY,KAAK;AAAA,QACf,QAAQ,KAAK;AAAA,QACb,UAAU,KAAK;AAAA,QACf,SAAS,WAAW,KAAK;AAAA,QACzB,MAAM;AAAA,QACN,MAAM,OAAO;AAAA,QACb,QAAQ,YAAY;AAAA,MACtB,CAAC;AAAA,IACH;AAAA,IAEA,WAAW,MAAwB;AACjC,YAAM,OAAO,QAAQ,kBAAkB,IAAI;AAC3C,aAAO,eAAe,SAAS,IAAI;AAAA,IACrC;AAAA,IAEA,WAAW,MAAwB;AACjC,YAAM,OAAO,QAAQ,kBAAkB,IAAI;AAC3C,YAAM,SAAS,KAAK,UAAU;AAC9B,UAAI,CAAC,OAAQ,QAAO;AACpB,YAAM,eAAe,OAAO,gBAAgB;AAC5C,UAAI,CAAC,gBAAgB,aAAa,WAAW,EAAG,QAAO;AACvD,aAAO,aAAa,KAAK,CAAC,MAAM,kBAAkB,CAAC,CAAC;AAAA,IACtD;AAAA,EACF;AACF;;;AP7DA,eAAsB,KAAK,SAA2C;AACpE,QAAM,WAAW,QAAQ,MAAM,SAAS,IAAI,QAAQ,QAAQ,CAAC,GAAG;AAChE,QAAM,QAAQ,SAAS,IAAI,CAAC,MAAM;AAChC,QAAI,MAAM,IAAK,QAAO;AACtB,QAAI,EAAE,SAAS,GAAG,EAAG,QAAO,GAAG,CAAC;AAChC,QAAI,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC,EAAE,SAAS,KAAK,KAAK,CAAC,EAAE,SAAS,MAAM,KAAK,CAAC,EAAE,SAAS,MAAM,KAAK,CAAC,EAAE,SAAS,MAAM,GAAG;AAC/G,aAAO,GAAG,CAAC;AAAA,IACb;AACA,WAAO;AAAA,EACT,CAAC;AAED,QAAM,QAAQ,MAAM,GAAG,OAAO;AAAA,IAC5B,QAAQ,CAAC,sBAAsB,cAAc,cAAc,aAAa,cAAc,YAAY;AAAA,IAClG,UAAU;AAAA,EACZ,CAAC;AAED,QAAM,cAAc,SAAS,OAAO,CAAC,MAAM;AACzC,QAAI,QAAQ,SAAS,CAAC,QAAQ,MAAM,SAAS,EAAE,EAAE,EAAG,QAAO;AAC3D,WAAO;AAAA,EACT,CAAC;AAED,QAAM,UAAU,YAAY,OAAO,QAAQ;AAC3C,QAAM,iBAAiB,YAAY,OAAO,CAAC,MAA0B,CAAC,SAAS,CAAC,CAAC;AACjF,QAAM,cAA4B,CAAC;AAGnC,QAAM,UAAU,MAAM,SAAS,IAAI,uBAAuB,KAAK,IAAI;AAGnE,MAAI,QAAQ,SAAS,KAAK,SAAS;AACjC,UAAM,UAAU,QAAQ,eAAe;AACvC,eAAW,QAAQ,OAAO;AACxB,YAAM,SAASC,cAAa,MAAM,MAAM;AACxC,YAAM,aAAa,QAAQ,cAAc,IAAI;AAC7C,UAAI,YAAY;AACd,cAAM,UAAU,WAAW,SAAS,YAAY,SAAS,QAAQ,IAAI;AACrE,oBAAY,KAAK,GAAG,OAAO;AAAA,MAC7B;AAAA,IACF;AAAA,EACF;AAGA,MAAI,eAAe,SAAS,KAAK,SAAS;AACxC,UAAM,eAAe,eAAe,OAAO;AAC3C,eAAW,QAAQ,gBAAgB;AACjC,YAAM,aAAa,KAAK,QAAQ,YAAY;AAC5C,iBAAW,KAAK,YAAY;AAC1B,cAAM,WAAW,aAAa,MAAM,IAAI,EAAE,IAAI;AAC9C,YAAI,aAAa,OAAW,UAAS,CAAC,CAAC,GAAG,SAAS,UAAU,SAAS,MAAM;AAAA,MAC9E;AACA,kBAAY,KAAK,GAAG,UAAU;AAAA,IAChC;AAAA,EACF;AAEA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,UAAwB,CAAC;AAC/B,aAAW,KAAK,aAAa;AAC3B,UAAM,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE,MAAM;AAC3C,QAAI,KAAK,IAAI,GAAG,EAAG;AACnB,SAAK,IAAI,GAAG;AACZ,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,QAAQ,QAAQ;AAClB,eAAW,KAAK,SAAS;AACvB,QAAE,WAAW;AAAA,IACf;AAAA,EACF;AAEA,SAAO,EAAE,aAAa,SAAS,WAAW,MAAM,OAAO;AACzD;AAOA,SAAS,SAAS,aAA2B,UAAyB,QAAsB;AAC1F,MAAI,SAAS,WAAW,KAAK,YAAY,WAAW,EAAG;AAEvD,QAAM,YAAY,oBAAI,IAA2B;AACjD,aAAW,KAAK,UAAU;AACxB,UAAM,UAAU,OAAO,QAAQ,EAAE,GAAG;AACpC,QAAI,OAAO,UAAU,IAAI,OAAO;AAChC,QAAI,SAAS,QAAW;AACtB,aAAO,CAAC;AACR,gBAAU,IAAI,SAAS,IAAI;AAAA,IAC7B;AACA,SAAK,KAAK,CAAC;AAAA,EACb;AAEA,aAAW,KAAK,aAAa;AAC3B,UAAM,SAAS,kBAAkB,EAAE,MAAM,SAAS;AAClD,QAAI,WAAW,MAAM;AACnB,QAAE,aAAa;AACf;AAAA,IACF;AACA,UAAM,QAAQ,kBAAkB,EAAE,OAAO,GAAG,WAAW,MAAM;AAC7D,QAAI,UAAU,KAAM,GAAE,aAAa;AAAA,EACrC;AACF;AAEA,SAAS,kBAAkB,UAAkB,WAAsD;AACjG,QAAM,iBAAiB,UAAU,IAAI,QAAQ;AAC7C,MAAI,mBAAmB,UAAa,eAAe,WAAW,EAAG,QAAO;AACxE,QAAM,UAAU,eAAe,GAAG,EAAE;AACpC,MAAI,YAAY,OAAW,QAAO;AAClC,MAAI,QAAQ,SAAS,OAAQ,QAAO;AACpC,QAAM,OAAO,QAAQ,MAAM,KAAK;AAChC,MAAI,KAAK,WAAW,SAAS,EAAG,QAAO;AACvC,SAAO;AACT;AAEA,SAAS,kBACP,gBACA,WACA,QACe;AACf,QAAM,iBAAiB,UAAU,IAAI,cAAc;AACnD,MAAI,mBAAmB,UAAa,eAAe,WAAW,EAAG,QAAO;AACxE,QAAM,UAAU,eAAe,GAAG,EAAE;AACpC,MAAI,YAAY,OAAW,QAAO;AAElC,MAAI,QAAQ,SAAS,SAAS;AAC5B,WAAO,kBAAkB,QAAQ,KAAK;AAAA,EACxC;AAEA,QAAM,QAAkB,CAAC,QAAQ,MAAM,KAAK,CAAC;AAC7C,MAAI,WAAW,iBAAiB;AAChC,aAAS;AACP,UAAM,OAAO,UAAU,IAAI,QAAQ;AACnC,QAAI,SAAS,UAAa,KAAK,WAAW,EAAG;AAC7C,UAAM,cAAc,KAAK,GAAG,EAAE;AAC9B,QAAI,gBAAgB,UAAa,YAAY,SAAS,OAAQ;AAC9D,QAAI,OAAO,QAAQ,YAAY,KAAK,MAAM,SAAU;AACpD,UAAM,QAAQ,YAAY,MAAM,KAAK,CAAC;AACtC;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,kBAAkB,OAAuB;AAChD,SAAO,MACJ,MAAM,IAAI,EACV,IAAI,CAAC,SAAS,KAAK,QAAQ,aAAa,EAAE,EAAE,KAAK,CAAC,EAClD,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAChC,KAAK,IAAI;AACd;AAEA,SAAS,OAAO,QAAgB,QAAwB;AACtD,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,UAAU,IAAI,OAAO,QAAQ,KAAK;AACpD,QAAI,OAAO,CAAC,MAAM,KAAM;AAAA,EAC1B;AACA,SAAO;AACT;","names":["ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","ts","readFileSync","ts","ts","visit","ts","dirname","ts","readFileSync"]}
package/dist/cli.js CHANGED
@@ -1,39 +1,44 @@
1
1
  import {
2
2
  scan
3
- } from "./chunk-NYM4SO7V.js";
3
+ } from "./chunk-YYX4R25B.js";
4
4
 
5
5
  // src/cli.ts
6
+ import { parseArgs } from "util";
6
7
  import { relative } from "path";
7
8
  import pc from "picocolors";
8
- var SEVERITY_RANK = { info: 0, warning: 1, error: 2 };
9
9
  function isSeverity(value) {
10
10
  return value === "info" || value === "warning" || value === "error";
11
11
  }
12
12
  async function main(argv) {
13
- const args = argv.slice(2);
14
- const first = args[0];
15
- if (first === "-h" || first === "--help") {
13
+ const rawArgs = argv.slice(2);
14
+ const userArgs = rawArgs[0] === "scan" ? rawArgs.slice(1) : rawArgs;
15
+ const { values, positionals: paths } = parseArgs({
16
+ args: userArgs,
17
+ options: {
18
+ strict: { type: "boolean", default: false },
19
+ filter: { type: "string" },
20
+ format: { type: "string", default: "grouped" },
21
+ severity: { type: "string", multiple: true, default: [] },
22
+ help: { type: "boolean", short: "h", default: false }
23
+ },
24
+ allowPositionals: true
25
+ });
26
+ if (values.help) {
16
27
  printHelp();
17
28
  return 0;
18
29
  }
19
- const userArgs = first === "scan" ? args.slice(1) : args;
20
- const strict = userArgs.includes("--strict");
21
- const filter = extractFlag(userArgs, "--filter");
22
- const format = extractFlag(userArgs, "--format", "grouped");
23
- const severityFilter = extractFlag(userArgs, "--severity");
24
- const paths = userArgs.filter((a) => !a.startsWith("--"));
25
30
  const result = await scan({
26
31
  paths,
27
- strict,
28
- rules: filter ? [filter] : void 0
32
+ strict: values.strict,
33
+ rules: values.filter ? [values.filter] : void 0
29
34
  });
30
- const minRank = severityFilter !== void 0 && isSeverity(severityFilter) ? SEVERITY_RANK[severityFilter] : 0;
31
- const filtered = minRank > 0 ? result.diagnostics.filter((d) => SEVERITY_RANK[d.severity] >= minRank) : result.diagnostics;
35
+ const severitySet = values.severity.length > 0 ? new Set(values.severity.filter(isSeverity)) : null;
36
+ const filtered = severitySet ? result.diagnostics.filter((d) => severitySet.has(d.severity)) : result.diagnostics;
32
37
  if (filtered.length === 0) {
33
38
  console.log(pc.green(`No issues found in ${result.fileCount} files.`));
34
39
  return 0;
35
40
  }
36
- if (format === "flat") {
41
+ if (values.format === "flat") {
37
42
  printDiagnosticsFlat(filtered);
38
43
  } else {
39
44
  printDiagnostics(filtered);
@@ -52,7 +57,7 @@ Usage:
52
57
  Options:
53
58
  --strict Treat all warnings as errors
54
59
  --filter <rule> Run only the specified rule
55
- --severity <level> Show only diagnostics at this level or above (error, warning, info)
60
+ --severity <level> Filter by severity (error, warning, info). Repeatable.
56
61
  --format <mode> Output format: grouped (default) or flat
57
62
  -h, --help Show this help
58
63
 
@@ -66,25 +71,9 @@ Examples:
66
71
  unguard scan src --strict
67
72
  unguard scan src --filter no-empty-catch
68
73
  unguard scan src --severity=error
74
+ unguard scan src --severity=error --severity=warning
69
75
  unguard scan src --format=flat | grep error`);
70
76
  }
71
- function extractFlag(args, flag, fallback) {
72
- for (let i = 0; i < args.length; i++) {
73
- const arg = args[i];
74
- if (arg === void 0) continue;
75
- if (arg === flag) {
76
- const value = args[i + 1];
77
- args.splice(i, 2);
78
- return value;
79
- }
80
- if (arg.startsWith(flag + "=")) {
81
- const value = arg.slice(flag.length + 1);
82
- args.splice(i, 1);
83
- return value;
84
- }
85
- }
86
- return fallback;
87
- }
88
77
  function printDiagnostics(diagnostics) {
89
78
  const byFile = /* @__PURE__ */ new Map();
90
79
  for (const d of diagnostics) {
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { relative } from \"node:path\";\nimport pc from \"picocolors\";\nimport { scan } from \"./engine.ts\";\nimport type { Diagnostic } from \"./rules/types.ts\";\n\ntype Severity = \"info\" | \"warning\" | \"error\";\nconst SEVERITY_RANK: Record<Severity, number> = { info: 0, warning: 1, error: 2 };\n\nfunction isSeverity(value: string): value is Severity {\n return value === \"info\" || value === \"warning\" || value === \"error\";\n}\n\nexport async function main(argv: string[]): Promise<number> {\n const args = argv.slice(2);\n const first = args[0];\n\n if (first === \"-h\" || first === \"--help\") {\n printHelp();\n return 0;\n }\n\n const userArgs = first === \"scan\" ? args.slice(1) : args;\n const strict = userArgs.includes(\"--strict\");\n const filter = extractFlag(userArgs, \"--filter\");\n const format = extractFlag(userArgs, \"--format\", \"grouped\");\n const severityFilter = extractFlag(userArgs, \"--severity\");\n const paths = userArgs.filter((a) => !a.startsWith(\"--\"));\n\n const result = await scan({\n paths,\n strict,\n rules: filter ? [filter] : undefined,\n });\n\n const minRank = severityFilter !== undefined && isSeverity(severityFilter) ? SEVERITY_RANK[severityFilter] : 0;\n const filtered = minRank > 0 ? result.diagnostics.filter((d) => SEVERITY_RANK[d.severity] >= minRank) : result.diagnostics;\n\n if (filtered.length === 0) {\n console.log(pc.green(`No issues found in ${result.fileCount} files.`));\n return 0;\n }\n\n if (format === \"flat\") {\n printDiagnosticsFlat(filtered);\n } else {\n printDiagnostics(filtered);\n }\n printSummary(filtered, result.fileCount);\n\n const hasError = filtered.some((d) => d.severity === \"error\");\n return hasError ? 2 : 1;\n}\n\nfunction printHelp() {\n console.log(`unguard: data-shape static analyzer\n\nUsage:\n unguard scan [paths...] [options]\n unguard [paths...] [options]\n\nOptions:\n --strict Treat all warnings as errors\n --filter <rule> Run only the specified rule\n --severity <level> Show only diagnostics at this level or above (error, warning, info)\n --format <mode> Output format: grouped (default) or flat\n -h, --help Show this help\n\nExit codes:\n 0 No issues\n 1 Warnings or info only\n 2 At least one error\n\nExamples:\n unguard scan src\n unguard scan src --strict\n unguard scan src --filter no-empty-catch\n unguard scan src --severity=error\n unguard scan src --format=flat | grep error`);\n}\n\nfunction extractFlag(args: string[], flag: string, fallback?: string): string | undefined {\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === undefined) continue;\n if (arg === flag) {\n const value = args[i + 1];\n args.splice(i, 2);\n return value;\n }\n if (arg.startsWith(flag + \"=\")) {\n const value = arg.slice(flag.length + 1);\n args.splice(i, 1);\n return value;\n }\n }\n return fallback;\n}\n\nfunction printDiagnostics(diagnostics: Diagnostic[]) {\n const byFile = new Map<string, Diagnostic[]>();\n for (const d of diagnostics) {\n let list = byFile.get(d.file);\n if (list === undefined) {\n list = [];\n byFile.set(d.file, list);\n }\n list.push(d);\n }\n\n const cwd = process.cwd() + \"/\";\n const cwdBase = process.cwd();\n\n for (const [file, diags] of byFile) {\n const rel = relative(cwdBase, file);\n console.log(pc.underline(rel));\n\n for (const d of diags) {\n const loc = `${d.line}:${d.column}`;\n const sev = d.severity === \"error\" ? pc.red(\"error\") : d.severity === \"warning\" ? pc.yellow(\"warning\") : pc.blue(\"info\");\n const msg = d.message.replaceAll(cwd, \"\");\n const rule = pc.dim(d.ruleId);\n const annotation = d.annotation !== undefined ? ` ${pc.cyan(d.annotation)}` : \"\";\n console.log(` ${pc.dim(loc.padEnd(10))} ${sev} ${msg} ${rule}${annotation}`);\n }\n\n console.log();\n }\n}\n\nfunction printDiagnosticsFlat(diagnostics: Diagnostic[]) {\n const cwdBase = process.cwd();\n const cwd = cwdBase + \"/\";\n\n for (const d of diagnostics) {\n const rel = relative(cwdBase, d.file);\n const msg = d.message.replaceAll(cwd, \"\");\n console.log(`${rel}:${d.line}:${d.column} ${d.severity} [${d.ruleId}] ${msg}`);\n }\n}\n\nfunction printSummary(diagnostics: Diagnostic[], fileCount: number) {\n const counts = new Map<string, number>();\n for (const d of diagnostics) {\n const prev = counts.get(d.ruleId);\n counts.set(d.ruleId, (prev === undefined ? 0 : prev) + 1);\n }\n\n const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1]);\n const maxRule = Math.max(...sorted.map(([r]) => r.length));\n\n console.log(pc.bold(\"Summary\"));\n for (const [rule, count] of sorted) {\n console.log(` ${rule.padEnd(maxRule)} ${pc.bold(String(count))}`);\n }\n\n console.log(\n `\\n${pc.bold(String(diagnostics.length))} issue${diagnostics.length === 1 ? \"\" : \"s\"} in ${fileCount} files.`,\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { parseArgs } from \"node:util\";\nimport { relative } from \"node:path\";\nimport pc from \"picocolors\";\nimport { scan } from \"./engine.ts\";\nimport type { Diagnostic } from \"./rules/types.ts\";\n\ntype Severity = \"info\" | \"warning\" | \"error\";\nfunction isSeverity(value: string): value is Severity {\n return value === \"info\" || value === \"warning\" || value === \"error\";\n}\n\nexport async function main(argv: string[]): Promise<number> {\n const rawArgs = argv.slice(2);\n const userArgs = rawArgs[0] === \"scan\" ? rawArgs.slice(1) : rawArgs;\n\n const { values, positionals: paths } = parseArgs({\n args: userArgs,\n options: {\n strict: { type: \"boolean\", default: false },\n filter: { type: \"string\" },\n format: { type: \"string\", default: \"grouped\" },\n severity: { type: \"string\", multiple: true, default: [] },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n allowPositionals: true,\n });\n\n if (values.help) {\n printHelp();\n return 0;\n }\n\n const result = await scan({\n paths,\n strict: values.strict,\n rules: values.filter ? [values.filter] : undefined,\n });\n\n const severitySet = values.severity.length > 0\n ? new Set(values.severity.filter(isSeverity))\n : null;\n const filtered = severitySet\n ? result.diagnostics.filter((d) => severitySet.has(d.severity))\n : result.diagnostics;\n\n if (filtered.length === 0) {\n console.log(pc.green(`No issues found in ${result.fileCount} files.`));\n return 0;\n }\n\n if (values.format === \"flat\") {\n printDiagnosticsFlat(filtered);\n } else {\n printDiagnostics(filtered);\n }\n printSummary(filtered, result.fileCount);\n\n const hasError = filtered.some((d) => d.severity === \"error\");\n return hasError ? 2 : 1;\n}\n\nfunction printHelp() {\n console.log(`unguard: data-shape static analyzer\n\nUsage:\n unguard scan [paths...] [options]\n unguard [paths...] [options]\n\nOptions:\n --strict Treat all warnings as errors\n --filter <rule> Run only the specified rule\n --severity <level> Filter by severity (error, warning, info). Repeatable.\n --format <mode> Output format: grouped (default) or flat\n -h, --help Show this help\n\nExit codes:\n 0 No issues\n 1 Warnings or info only\n 2 At least one error\n\nExamples:\n unguard scan src\n unguard scan src --strict\n unguard scan src --filter no-empty-catch\n unguard scan src --severity=error\n unguard scan src --severity=error --severity=warning\n unguard scan src --format=flat | grep error`);\n}\n\n\nfunction printDiagnostics(diagnostics: Diagnostic[]) {\n const byFile = new Map<string, Diagnostic[]>();\n for (const d of diagnostics) {\n let list = byFile.get(d.file);\n if (list === undefined) {\n list = [];\n byFile.set(d.file, list);\n }\n list.push(d);\n }\n\n const cwd = process.cwd() + \"/\";\n const cwdBase = process.cwd();\n\n for (const [file, diags] of byFile) {\n const rel = relative(cwdBase, file);\n console.log(pc.underline(rel));\n\n for (const d of diags) {\n const loc = `${d.line}:${d.column}`;\n const sev = d.severity === \"error\" ? pc.red(\"error\") : d.severity === \"warning\" ? pc.yellow(\"warning\") : pc.blue(\"info\");\n const msg = d.message.replaceAll(cwd, \"\");\n const rule = pc.dim(d.ruleId);\n const annotation = d.annotation !== undefined ? ` ${pc.cyan(d.annotation)}` : \"\";\n console.log(` ${pc.dim(loc.padEnd(10))} ${sev} ${msg} ${rule}${annotation}`);\n }\n\n console.log();\n }\n}\n\nfunction printDiagnosticsFlat(diagnostics: Diagnostic[]) {\n const cwdBase = process.cwd();\n const cwd = cwdBase + \"/\";\n\n for (const d of diagnostics) {\n const rel = relative(cwdBase, d.file);\n const msg = d.message.replaceAll(cwd, \"\");\n console.log(`${rel}:${d.line}:${d.column} ${d.severity} [${d.ruleId}] ${msg}`);\n }\n}\n\nfunction printSummary(diagnostics: Diagnostic[], fileCount: number) {\n const counts = new Map<string, number>();\n for (const d of diagnostics) {\n const prev = counts.get(d.ruleId);\n counts.set(d.ruleId, (prev === undefined ? 0 : prev) + 1);\n }\n\n const sorted = [...counts.entries()].sort((a, b) => b[1] - a[1]);\n const maxRule = Math.max(...sorted.map(([r]) => r.length));\n\n console.log(pc.bold(\"Summary\"));\n for (const [rule, count] of sorted) {\n console.log(` ${rule.padEnd(maxRule)} ${pc.bold(String(count))}`);\n }\n\n console.log(\n `\\n${pc.bold(String(diagnostics.length))} issue${diagnostics.length === 1 ? \"\" : \"s\"} in ${fileCount} files.`,\n );\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,17 +1,17 @@
1
- import { Node, Comment } from 'oxc-parser';
1
+ import * as ts from 'typescript';
2
2
 
3
3
  interface TypeEntry {
4
4
  name: string;
5
5
  file: string;
6
6
  line: number;
7
7
  hash: string;
8
- node: Node;
8
+ node: ts.Node;
9
9
  exported: boolean;
10
10
  }
11
11
  declare class TypeRegistry {
12
12
  private entries;
13
13
  private byHash;
14
- add(name: string, file: string, line: number, typeNode: Node, source: string, exported: boolean): void;
14
+ add(name: string, file: string, line: number, typeNode: ts.Node, sourceFile: ts.SourceFile, exported: boolean): void;
15
15
  getDuplicateGroups(): TypeEntry[][];
16
16
  getAll(): TypeEntry[];
17
17
  getNameCollisionGroups(): TypeEntry[][];
@@ -29,8 +29,9 @@ interface FunctionEntry {
29
29
  line: number;
30
30
  hash: string;
31
31
  params: ParamInfo[];
32
- node: Node;
32
+ node: ts.Node;
33
33
  exported: boolean;
34
+ symbol?: ts.Symbol;
34
35
  }
35
36
  declare class FunctionRegistry {
36
37
  private entries;
@@ -42,6 +43,12 @@ declare class FunctionRegistry {
42
43
  getNameCollisionGroups(): FunctionEntry[][];
43
44
  }
44
45
 
46
+ interface CommentInfo {
47
+ type: "Line" | "Block";
48
+ value: string;
49
+ start: number;
50
+ end: number;
51
+ }
45
52
  interface ProjectIndex {
46
53
  types: TypeRegistry;
47
54
  functions: FunctionRegistry;
@@ -49,8 +56,8 @@ interface ProjectIndex {
49
56
  imports: ImportEntry[];
50
57
  files: Map<string, {
51
58
  source: string;
52
- program: Node;
53
- comments: Comment[];
59
+ sourceFile: ts.SourceFile;
60
+ comments: CommentInfo[];
54
61
  }>;
55
62
  }
56
63
  interface CallSite {
@@ -58,7 +65,8 @@ interface CallSite {
58
65
  file: string;
59
66
  line: number;
60
67
  argCount: number;
61
- node: Node;
68
+ node: ts.CallExpression;
69
+ symbol?: ts.Symbol;
62
70
  }
63
71
  interface ImportEntry {
64
72
  file: string;
@@ -76,22 +84,22 @@ interface Diagnostic {
76
84
  column: number;
77
85
  annotation?: string;
78
86
  }
79
- interface Span {
80
- start: number;
81
- end: number;
82
- }
83
- interface VisitContext {
84
- report(span: Span, message?: string): void;
87
+ interface TSVisitContext {
88
+ report(node: ts.Node, message?: string): void;
89
+ reportAtOffset(offset: number, message?: string): void;
85
90
  filename: string;
86
91
  source: string;
87
- comments: Comment[];
92
+ sourceFile: ts.SourceFile;
93
+ checker: ts.TypeChecker;
94
+ isNullable(node: ts.Node): boolean;
95
+ isExternal(node: ts.Node): boolean;
88
96
  }
89
- interface SingleFileRule {
97
+ interface TSRule {
98
+ kind: "ts";
90
99
  id: string;
91
100
  severity: "info" | "warning" | "error";
92
101
  message: string;
93
- visit(node: Node, parent: Node | null, ctx: VisitContext): void;
94
- visitComment?(comment: Comment, ctx: VisitContext): void;
102
+ visit(node: ts.Node, ctx: TSVisitContext): void;
95
103
  }
96
104
 
97
105
  interface CrossFileRule {
@@ -100,7 +108,7 @@ interface CrossFileRule {
100
108
  message: string;
101
109
  analyze(project: ProjectIndex): Diagnostic[];
102
110
  }
103
- type Rule = SingleFileRule | CrossFileRule;
111
+ type Rule = CrossFileRule | TSRule;
104
112
 
105
113
  interface ScanOptions {
106
114
  paths: string[];
@@ -115,4 +123,4 @@ declare function scan(options: ScanOptions): Promise<ScanResult>;
115
123
 
116
124
  declare const allRules: Rule[];
117
125
 
118
- export { type CrossFileRule, type Diagnostic, type Rule, type ScanOptions, type ScanResult, type SingleFileRule, allRules, scan };
126
+ export { type CrossFileRule, type Diagnostic, type Rule, type ScanOptions, type ScanResult, type TSRule, type TSVisitContext, allRules, scan };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  allRules,
3
3
  scan
4
- } from "./chunk-NYM4SO7V.js";
4
+ } from "./chunk-YYX4R25B.js";
5
5
  export {
6
6
  allRules,
7
7
  scan
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unguard",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Detect overdefensive AI-generated code",
5
5
  "author": "Seva Maltsev <me@seva.dev> (https://seva.dev)",
6
6
  "license": "MIT",
@@ -47,15 +47,12 @@
47
47
  },
48
48
  "dependencies": {
49
49
  "fast-glob": "^3.3.3",
50
- "oxc-parser": "^0.114.0",
51
- "oxc-walker": "^0.7.0",
52
- "picocolors": "^1.1.1"
50
+ "picocolors": "^1.1.1",
51
+ "typescript": "^5.9.3"
53
52
  },
54
53
  "devDependencies": {
55
- "@oxc-project/types": "^0.114.0",
56
54
  "@types/node": "^25.3.0",
57
55
  "tsup": "^8.5.1",
58
- "typescript": "^5.9.3",
59
56
  "vitest": "^4.0.18"
60
57
  }
61
58
  }