callograph 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,7 +21,7 @@ Callograph provides structural visibility into function relationships and propag
21
21
 
22
22
  ---
23
23
 
24
- ## Core Capabilities (v0.0.3)
24
+ ## Core Capabilities (v0.0.4)
25
25
 
26
26
  ### 1. Call Graph Construction
27
27
 
@@ -231,7 +231,7 @@ It prioritizes:
231
231
 
232
232
  ---
233
233
 
234
- ## Limitations (v0.0.3)
234
+ ## Limitations (v0.0.4)
235
235
 
236
236
  * Dynamic runtime dispatch cannot always be resolved.
237
237
  * Higher-order functions are partially supported.
package/dist/cli.cjs CHANGED
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
1
3
  "use strict";
2
4
  var __create = Object.create;
3
5
  var __defProp = Object.defineProperty;
package/dist/cli.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/analyzer/createProgram.ts","../src/analyzer/collectFunctions.ts","../src/analyzer/buildCallGraph.ts","../src/analyzer/purity.ts","../src/analyzer/helpers/globals.ts","../src/analyzer/helpers/ast.ts","../src/analyzer/helpers/async.ts","../src/analyzer/propagateImpurity.ts","../src/cli.ts"],"sourcesContent":["import ts from \"typescript\";\r\n\r\nexport function createTsProgram(tsconfigPath: string) {\r\n const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\r\n\r\n if (configFile.error) {\r\n throw new Error(\r\n ts.flattenDiagnosticMessageText(configFile.error.messageText, \"\\n\")\r\n );\r\n }\r\n\r\n const parsedConfig = ts.parseJsonConfigFileContent(\r\n configFile.config,\r\n ts.sys,\r\n process.cwd()\r\n );\r\n\r\n return ts.createProgram({\r\n rootNames: parsedConfig.fileNames,\r\n options: parsedConfig.options,\r\n });\r\n}\r\n","import ts from \"typescript\";\r\nimport { FunctionNode } from \"../../libs/types\";\r\n\r\nfunction safeName(node: ts.FunctionLikeDeclaration, sf: ts.SourceFile): string {\r\n const anyNode = node as any;\r\n\r\n // Named declarations / methods: function foo() {} / class X { foo() {} }\r\n if (anyNode.name && ts.isIdentifier(anyNode.name)) return anyNode.name.text;\r\n\r\n // Methods with string/numeric names: class X { \"foo\"() {} }\r\n if (\r\n anyNode.name &&\r\n (ts.isStringLiteral(anyNode.name) || ts.isNumericLiteral(anyNode.name))\r\n ) {\r\n return String(anyNode.name.text);\r\n }\r\n\r\n // const foo = () => {} / const foo = function() {}\r\n const p = node.parent;\r\n if (p && ts.isVariableDeclaration(p) && ts.isIdentifier(p.name)) return p.name.text;\r\n\r\n // obj.foo = function() {}\r\n if (p && ts.isBinaryExpression(p) && ts.isPropertyAccessExpression(p.left)) {\r\n return p.left.name.text;\r\n }\r\n\r\n const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf));\r\n return `<anonymous@${line + 1}:${character + 1}>`;\r\n}\r\n\r\nexport function collectFunctions(program: ts.Program): FunctionNode[] {\r\n const out: FunctionNode[] = [];\r\n\r\n for (const sf of program.getSourceFiles()) {\r\n if (sf.isDeclarationFile) continue;\r\n if (sf.fileName.includes(\"node_modules\")) continue;\r\n\r\n ts.forEachChild(sf, function visit(n) {\r\n const isNamedFunctionDecl = ts.isFunctionDeclaration(n) && !!n.name;\r\n const isMethod = ts.isMethodDeclaration(n);\r\n\r\n const p = n.parent; //guard parent\r\n const isVarAssigned =\r\n (ts.isArrowFunction(n) || ts.isFunctionExpression(n)) &&\r\n !!p &&\r\n ts.isVariableDeclaration(p) &&\r\n ts.isIdentifier(p.name);\r\n\r\n if (isNamedFunctionDecl || isMethod || isVarAssigned) {\r\n const name = safeName(n, sf);\r\n const id = `${sf.fileName}:${n.pos}:${name}`;\r\n out.push({ id, name, file: sf.fileName, node: n });\r\n }\r\n\r\n ts.forEachChild(n, visit);\r\n });\r\n }\r\n\r\n return out;\r\n}\r\n","import ts from \"typescript\";\r\nimport { FunctionNode } from \"../../libs/types\";\r\n\r\n// callerId -> calleeIds\r\nexport type CallGraph = Map<string, Set<string>>;\r\n\r\nfunction isProjectFile(fileName: string) {\r\n return !fileName.includes(\"node_modules\") && !fileName.endsWith(\".d.ts\");\r\n}\r\n\r\nexport function buildCallGraph(\r\n program: ts.Program,\r\n functions: FunctionNode[]\r\n): CallGraph {\r\n\r\n const checker = program.getTypeChecker();\r\n const graph: CallGraph = new Map();\r\n\r\n const declToId = new Map<ts.Declaration, string>();\r\n\r\n for (const fn of functions) {\r\n graph.set(fn.id, new Set());\r\n declToId.set(fn.node, fn.id);\r\n }\r\n\r\n for (const fn of functions) {\r\n const callerId = fn.id;\r\n\r\n function visit(node: ts.Node) {\r\n if (ts.isCallExpression(node)) {\r\n const sig = checker.getResolvedSignature(node);\r\n const decl = sig?.getDeclaration();\r\n\r\n if (\r\n decl &&\r\n isProjectFile(decl.getSourceFile().fileName)\r\n ) {\r\n const calleeId = declToId.get(decl as ts.Declaration);\r\n\r\n if (calleeId) {\r\n graph.get(callerId)!.add(calleeId);\r\n }\r\n }\r\n }\r\n\r\n ts.forEachChild(node, visit);\r\n }\r\n\r\n visit(fn.node);\r\n }\r\n\r\n return graph;\r\n}\r\n","import ts from \"typescript\";\r\nimport { FunctionNode, PurityResult } from \"../../libs/types\";\r\nimport { GLOBAL_OBJECTS, MUTATING_ARRAY_METHODS } from \"./helpers/globals\";\r\nimport { isInside, rootIdentifier, isWriteOperator } from \"./helpers/ast\";\r\nimport {\r\n isPromiseLike,\r\n isAwaited,\r\n isReturned,\r\n isHandledWithThen,\r\n isAsyncForEach,\r\n isAsyncEventListener,\r\n isPromiseAll,\r\n} from \"./helpers/async\";\r\n\r\nexport function analyzePurity(\r\n program: ts.Program,\r\n functions: FunctionNode[]\r\n): PurityResult[] {\r\n const checker = program.getTypeChecker();\r\n const results: PurityResult[] = [];\r\n\r\n for (const fn of functions) {\r\n const sf = fn.node.getSourceFile();\r\n const { line, character } = sf.getLineAndCharacterOfPosition(\r\n fn.node.getStart(sf)\r\n );\r\n\r\n const reasons: string[] = [];\r\n const paramNames = new Set<string>();\r\n\r\n for (const p of fn.node.parameters) {\r\n if (ts.isIdentifier(p.name)) paramNames.add(p.name.text);\r\n }\r\n\r\n const mark = (reason: string) => {\r\n if (!reasons.includes(reason)) reasons.push(reason);\r\n };\r\n\r\n let containsAwait = false;\r\n let hasTryCatch = false;\r\n\r\n const isAsyncFunction =\r\n fn.node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;\r\n\r\n function visit(node: ts.Node): void {\r\n /* ---------------------------\r\n WRITE DETECTION\r\n ----------------------------*/\r\n if (ts.isBinaryExpression(node) && isWriteOperator(node.operatorToken.kind)) {\r\n const left = node.left as ts.Expression;\r\n const root = rootIdentifier(left);\r\n\r\n if (root && paramNames.has(root.text)) {\r\n mark(`Mutates parameter \\`${root.text}\\``);\r\n }\r\n\r\n if (ts.isIdentifier(left)) {\r\n const sym = checker.getSymbolAtLocation(left);\r\n const decls = sym?.getDeclarations() ?? [];\r\n const declaredInside = decls.some(d => isInside(d, fn.node));\r\n if (sym && decls.length > 0 && !declaredInside) {\r\n mark(`Writes to outer scope \\`${left.text}\\``);\r\n }\r\n }\r\n\r\n if (\r\n ts.isPropertyAccessExpression(left) &&\r\n left.expression.kind === ts.SyntaxKind.ThisKeyword\r\n ) {\r\n mark(`Mutates \\`this.${left.name.text}\\``);\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n ARRAY PARAM MUTATION\r\n ----------------------------*/\r\n if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {\r\n const method = node.expression.name.text;\r\n const root = rootIdentifier(node.expression.expression);\r\n\r\n if (root && paramNames.has(root.text) && MUTATING_ARRAY_METHODS.has(method)) {\r\n mark(`Mutates parameter \\`${root.text}\\` via \\`${method}()\\``);\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n GLOBAL ACCESS\r\n ----------------------------*/\r\n if (ts.isIdentifier(node)) {\r\n const sym = checker.getSymbolAtLocation(node);\r\n if (sym && GLOBAL_OBJECTS.has(sym.getName())) {\r\n mark(`Accesses global \\`${sym.getName()}\\``);\r\n }\r\n }\r\n\r\n if (ts.isPropertyAccessExpression(node)) {\r\n const root = rootIdentifier(node.expression);\r\n\r\n if (root?.text === \"process\" && node.name.text === \"env\") {\r\n mark(\"Accesses `process.env` (non-deterministic)\");\r\n }\r\n\r\n if (root?.text === \"console\") {\r\n mark(\"Writes to console\");\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n CALL EXPRESSIONS\r\n ----------------------------*/\r\n if (ts.isCallExpression(node)) {\r\n const type = checker.getTypeAtLocation(node);\r\n const expr = node.expression;\r\n\r\n // Non-deterministic\r\n if (\r\n ts.isPropertyAccessExpression(expr) &&\r\n ts.isIdentifier(expr.expression) &&\r\n expr.expression.text === \"Date\" &&\r\n expr.name.text === \"now\"\r\n ) {\r\n mark(\"Calls Date.now() (non-deterministic)\");\r\n }\r\n\r\n if (\r\n ts.isPropertyAccessExpression(expr) &&\r\n ts.isIdentifier(expr.expression) &&\r\n expr.expression.text === \"Math\" &&\r\n expr.name.text === \"random\"\r\n ) {\r\n mark(\"Calls Math.random() (non-deterministic)\");\r\n }\r\n\r\n // Async rules\r\n if (isPromiseLike(type, checker)) {\r\n if (!isAwaited(node) && !isReturned(node) && !isHandledWithThen(node)) {\r\n mark(\"Floating Promise (result ignored)\");\r\n }\r\n }\r\n\r\n if (isPromiseAll(node) && !isAwaited(node) && !isReturned(node)) {\r\n mark(\"Unawaited Promise.all()\");\r\n }\r\n\r\n if (isAsyncForEach(node)) {\r\n mark(\"Async callback inside forEach (not awaited)\");\r\n }\r\n\r\n if (isAsyncEventListener(node)) {\r\n mark(\"Async event listener without error boundary\");\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n NEW EXPRESSIONS\r\n ----------------------------*/\r\n if (ts.isNewExpression(node)) {\r\n if (ts.isIdentifier(node.expression) && node.expression.text === \"Date\") {\r\n mark(\"Instantiates Date (non-deterministic)\");\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n AWAIT + ERROR HANDLING\r\n ----------------------------*/\r\n if (ts.isAwaitExpression(node)) {\r\n containsAwait = true;\r\n }\r\n\r\n if (ts.isTryStatement(node) && node.catchClause) {\r\n hasTryCatch = true;\r\n }\r\n\r\n if (ts.isCatchClause(node) && node.block.statements.length === 0) {\r\n mark(\"Empty catch block (error swallowed)\");\r\n }\r\n\r\n ts.forEachChild(node, visit);\r\n }\r\n\r\n visit(fn.node);\r\n\r\n if (isAsyncFunction && !containsAwait) {\r\n mark(\"Async function without await\");\r\n }\r\n\r\n if (isAsyncFunction && containsAwait && !hasTryCatch) {\r\n mark(\"Async function without error handling (no try/catch)\");\r\n }\r\n\r\n results.push({\r\n id: fn.id,\r\n name: fn.name,\r\n file: fn.file,\r\n line: line + 1,\r\n character: character + 1,\r\n isPure: reasons.length === 0,\r\n reasons,\r\n });\r\n }\r\n\r\n return results;\r\n}\r\n","export const GLOBAL_OBJECTS = new Set([\r\n \"window\",\r\n \"document\",\r\n \"global\",\r\n \"process\",\r\n \"localStorage\",\r\n \"sessionStorage\",\r\n]);\r\n\r\nexport const MUTATING_ARRAY_METHODS = new Set([\r\n \"push\",\r\n \"pop\",\r\n \"splice\",\r\n \"shift\",\r\n \"unshift\",\r\n \"sort\",\r\n \"reverse\",\r\n \"copyWithin\",\r\n \"fill\",\r\n]);\r\n","import ts from \"typescript\";\r\n\r\nexport function isInside(node: ts.Node, container: ts.Node) {\r\n return node.pos >= container.pos && node.end <= container.end;\r\n}\r\n\r\nexport function rootIdentifier(expr: ts.Expression): ts.Identifier | null {\r\n if (ts.isIdentifier(expr)) return expr;\r\n if (ts.isPropertyAccessExpression(expr)) return rootIdentifier(expr.expression);\r\n if (ts.isElementAccessExpression(expr)) return rootIdentifier(expr.expression);\r\n if (ts.isParenthesizedExpression(expr)) return rootIdentifier(expr.expression);\r\n return null;\r\n}\r\n\r\nexport function isWriteOperator(kind: ts.SyntaxKind) {\r\n return (\r\n kind === ts.SyntaxKind.EqualsToken ||\r\n kind === ts.SyntaxKind.PlusEqualsToken ||\r\n kind === ts.SyntaxKind.MinusEqualsToken ||\r\n kind === ts.SyntaxKind.AsteriskEqualsToken ||\r\n kind === ts.SyntaxKind.SlashEqualsToken ||\r\n kind === ts.SyntaxKind.PercentEqualsToken ||\r\n kind === ts.SyntaxKind.AmpersandEqualsToken ||\r\n kind === ts.SyntaxKind.BarEqualsToken ||\r\n kind === ts.SyntaxKind.CaretEqualsToken ||\r\n kind === ts.SyntaxKind.LessThanLessThanEqualsToken ||\r\n kind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken ||\r\n kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken\r\n );\r\n}\r\n","import ts from \"typescript\";\r\n\r\nexport function isPromiseLike(type: ts.Type, checker: ts.TypeChecker): boolean {\r\n if (!type) return false;\r\n\r\n const thenProp = type.getProperty(\"then\");\r\n if (!thenProp) return false;\r\n\r\n const decl = thenProp.valueDeclaration ?? thenProp.declarations?.[0];\r\n if (!decl) return false;\r\n\r\n const thenType = checker.getTypeOfSymbolAtLocation(thenProp, decl);\r\n return thenType.getCallSignatures().length > 0;\r\n}\r\n\r\nexport function isAwaited(node: ts.CallExpression) {\r\n return ts.isAwaitExpression(node.parent);\r\n}\r\n\r\nexport function isReturned(node: ts.CallExpression) {\r\n return ts.isReturnStatement(node.parent);\r\n}\r\n\r\nexport function isHandledWithThen(node: ts.CallExpression) {\r\n if (!ts.isPropertyAccessExpression(node.parent)) return false;\r\n const name = node.parent.name.text;\r\n return name === \"then\" || name === \"catch\" || name === \"finally\";\r\n}\r\n\r\nexport function isAsyncForEach(node: ts.CallExpression): boolean {\r\n if (!ts.isPropertyAccessExpression(node.expression)) return false;\r\n if (node.expression.name.text !== \"forEach\") return false;\r\n\r\n const cb = node.arguments[0];\r\n if (!cb) return false;\r\n\r\n if (ts.isArrowFunction(cb) || ts.isFunctionExpression(cb)) {\r\n return cb.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;\r\n }\r\n\r\n return false;\r\n}\r\n\r\nexport function isAsyncEventListener(node: ts.CallExpression): boolean {\r\n if (!ts.isPropertyAccessExpression(node.expression)) return false;\r\n if (node.expression.name.text !== \"addEventListener\") return false;\r\n\r\n const cb = node.arguments[1];\r\n if (!cb) return false;\r\n\r\n if (ts.isArrowFunction(cb) || ts.isFunctionExpression(cb)) {\r\n return cb.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;\r\n }\r\n\r\n return false;\r\n}\r\n\r\nexport function isPromiseAll(node: ts.CallExpression): boolean {\r\n return (\r\n ts.isPropertyAccessExpression(node.expression) &&\r\n ts.isIdentifier(node.expression.expression) &&\r\n node.expression.expression.text === \"Promise\" &&\r\n node.expression.name.text === \"all\"\r\n );\r\n}\r\n","import { CallGraph } from \"./buildCallGraph\";\r\nimport { PurityResult } from \"../../libs/types\";\r\n\r\nexport function propagateImpurity(\r\n graph: CallGraph,\r\n purity: PurityResult[]\r\n): PurityResult[] {\r\n\r\n // Map functionId -> PurityResult\r\n const resultMap = new Map<string, PurityResult>();\r\n for (const r of purity) {\r\n resultMap.set(r.id, r);\r\n }\r\n\r\n // Build reverse graph: calleeId -> Set<callerId>\r\n const reverse = new Map<string, Set<string>>();\r\n\r\n for (const [callerId, calleeIds] of graph.entries()) {\r\n for (const calleeId of calleeIds) {\r\n if (!reverse.has(calleeId)) {\r\n reverse.set(calleeId, new Set());\r\n }\r\n reverse.get(calleeId)!.add(callerId);\r\n }\r\n }\r\n\r\n // BFS propagation\r\n const queue: string[] = [];\r\n\r\n // Seed with initially impure functions\r\n for (const r of purity) {\r\n if (!r.isPure) {\r\n queue.push(r.id);\r\n }\r\n }\r\n\r\n while (queue.length > 0) {\r\n const impureId = queue.shift()!;\r\n const callers = reverse.get(impureId);\r\n if (!callers) continue;\r\n\r\n for (const callerId of callers) {\r\n const caller = resultMap.get(callerId);\r\n if (!caller) continue;\r\n\r\n if (caller.isPure) {\r\n caller.isPure = false;\r\n caller.reasons.push(`Calls impure function`);\r\n queue.push(callerId);\r\n }\r\n }\r\n }\r\n\r\n return Array.from(resultMap.values());\r\n}\r\n","import { createTsProgram } from \"./analyzer/createProgram\";\r\nimport { collectFunctions } from \"./analyzer/collectFunctions\";\r\nimport { buildCallGraph } from \"./analyzer/buildCallGraph\";\r\nimport { analyzePurity } from \"./analyzer/purity\";\r\nimport { propagateImpurity } from \"./analyzer/propagateImpurity\";\r\n\r\nconst command = process.argv[2];\r\n\r\nif (command !== \"analyze\") {\r\n console.log(\"Usage: callograph analyze\");\r\n process.exit(1);\r\n}\r\n\r\nconst program = createTsProgram(\"tsconfig.json\");\r\nconst functions = collectFunctions(program);\r\nconst graph = buildCallGraph(program, functions);\r\n\r\nlet purity = analyzePurity(program, functions);\r\npurity = propagateImpurity(graph, purity);\r\n\r\n/* ------------------------------\r\n CALL GRAPH\r\n--------------------------------*/\r\nconsole.log(\"\\nCall Graph:\\n\");\r\n\r\nlet edgeCount = 0;\r\n\r\nfor (const [callerId, callees] of graph.entries()) {\r\n const caller = functions.find(f => f.id === callerId);\r\n const callerName = caller ? caller.name : callerId;\r\n\r\n for (const calleeId of callees) {\r\n const callee = functions.find(f => f.id === calleeId);\r\n const calleeName = callee ? callee.name : calleeId;\r\n\r\n console.log(`${callerName} → ${calleeName}`);\r\n edgeCount++;\r\n }\r\n}\r\n\r\nif (edgeCount === 0) {\r\n console.log(\"(no project-level call edges found)\");\r\n}\r\n\r\nconsole.log(`\\nTotal call edges: ${edgeCount}`);\r\n\r\n/* ------------------------------\r\n PURITY REPORT\r\n--------------------------------*/\r\nconsole.log(\"\\nPurity Report:\\n\");\r\n\r\nconst impure = purity.filter(r => !r.isPure);\r\n\r\nif (impure.length === 0) {\r\n console.log(\"All analyzed functions are pure.\");\r\n} else {\r\n for (const r of impure) {\r\n console.log(`${r.file}:${r.line}:${r.character}`);\r\n console.log(`Function: ${r.name}`);\r\n console.log(`Status: X Impure`);\r\n console.log(\"Reasons:\");\r\n for (const reason of r.reasons) {\r\n console.log(` - ${reason}`);\r\n }\r\n console.log(\"\");\r\n }\r\n}\r\n"],"mappings":"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","names":["ts","import_typescript","ts","program","import_typescript","program","functions","graph","visit","ts","import_typescript","import_typescript","ts","import_typescript","ts","program","functions","visit","ts","graph","purity"]}
1
+ {"version":3,"sources":["../src/analyzer/createProgram.ts","../src/analyzer/collectFunctions.ts","../src/analyzer/buildCallGraph.ts","../src/analyzer/purity.ts","../src/analyzer/helpers/globals.ts","../src/analyzer/helpers/ast.ts","../src/analyzer/helpers/async.ts","../src/analyzer/propagateImpurity.ts","../src/cli.ts"],"sourcesContent":["import ts from \"typescript\";\r\n\r\nexport function createTsProgram(tsconfigPath: string) {\r\n const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\r\n\r\n if (configFile.error) {\r\n throw new Error(\r\n ts.flattenDiagnosticMessageText(configFile.error.messageText, \"\\n\")\r\n );\r\n }\r\n\r\n const parsedConfig = ts.parseJsonConfigFileContent(\r\n configFile.config,\r\n ts.sys,\r\n process.cwd()\r\n );\r\n\r\n return ts.createProgram({\r\n rootNames: parsedConfig.fileNames,\r\n options: parsedConfig.options,\r\n });\r\n}\r\n","import ts from \"typescript\";\r\nimport { FunctionNode } from \"../../libs/types\";\r\n\r\nfunction safeName(node: ts.FunctionLikeDeclaration, sf: ts.SourceFile): string {\r\n const anyNode = node as any;\r\n\r\n // Named declarations / methods: function foo() {} / class X { foo() {} }\r\n if (anyNode.name && ts.isIdentifier(anyNode.name)) return anyNode.name.text;\r\n\r\n // Methods with string/numeric names: class X { \"foo\"() {} }\r\n if (\r\n anyNode.name &&\r\n (ts.isStringLiteral(anyNode.name) || ts.isNumericLiteral(anyNode.name))\r\n ) {\r\n return String(anyNode.name.text);\r\n }\r\n\r\n // const foo = () => {} / const foo = function() {}\r\n const p = node.parent;\r\n if (p && ts.isVariableDeclaration(p) && ts.isIdentifier(p.name)) return p.name.text;\r\n\r\n // obj.foo = function() {}\r\n if (p && ts.isBinaryExpression(p) && ts.isPropertyAccessExpression(p.left)) {\r\n return p.left.name.text;\r\n }\r\n\r\n const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf));\r\n return `<anonymous@${line + 1}:${character + 1}>`;\r\n}\r\n\r\nexport function collectFunctions(program: ts.Program): FunctionNode[] {\r\n const out: FunctionNode[] = [];\r\n\r\n for (const sf of program.getSourceFiles()) {\r\n if (sf.isDeclarationFile) continue;\r\n if (sf.fileName.includes(\"node_modules\")) continue;\r\n\r\n ts.forEachChild(sf, function visit(n) {\r\n const isNamedFunctionDecl = ts.isFunctionDeclaration(n) && !!n.name;\r\n const isMethod = ts.isMethodDeclaration(n);\r\n\r\n const p = n.parent; //guard parent\r\n const isVarAssigned =\r\n (ts.isArrowFunction(n) || ts.isFunctionExpression(n)) &&\r\n !!p &&\r\n ts.isVariableDeclaration(p) &&\r\n ts.isIdentifier(p.name);\r\n\r\n if (isNamedFunctionDecl || isMethod || isVarAssigned) {\r\n const name = safeName(n, sf);\r\n const id = `${sf.fileName}:${n.pos}:${name}`;\r\n out.push({ id, name, file: sf.fileName, node: n });\r\n }\r\n\r\n ts.forEachChild(n, visit);\r\n });\r\n }\r\n\r\n return out;\r\n}\r\n","import ts from \"typescript\";\r\nimport { FunctionNode } from \"../../libs/types\";\r\n\r\n// callerId -> calleeIds\r\nexport type CallGraph = Map<string, Set<string>>;\r\n\r\nfunction isProjectFile(fileName: string) {\r\n return !fileName.includes(\"node_modules\") && !fileName.endsWith(\".d.ts\");\r\n}\r\n\r\nexport function buildCallGraph(\r\n program: ts.Program,\r\n functions: FunctionNode[]\r\n): CallGraph {\r\n\r\n const checker = program.getTypeChecker();\r\n const graph: CallGraph = new Map();\r\n\r\n const declToId = new Map<ts.Declaration, string>();\r\n\r\n for (const fn of functions) {\r\n graph.set(fn.id, new Set());\r\n declToId.set(fn.node, fn.id);\r\n }\r\n\r\n for (const fn of functions) {\r\n const callerId = fn.id;\r\n\r\n function visit(node: ts.Node) {\r\n if (ts.isCallExpression(node)) {\r\n const sig = checker.getResolvedSignature(node);\r\n const decl = sig?.getDeclaration();\r\n\r\n if (\r\n decl &&\r\n isProjectFile(decl.getSourceFile().fileName)\r\n ) {\r\n const calleeId = declToId.get(decl as ts.Declaration);\r\n\r\n if (calleeId) {\r\n graph.get(callerId)!.add(calleeId);\r\n }\r\n }\r\n }\r\n\r\n ts.forEachChild(node, visit);\r\n }\r\n\r\n visit(fn.node);\r\n }\r\n\r\n return graph;\r\n}\r\n","import ts from \"typescript\";\r\nimport { FunctionNode, PurityResult } from \"../../libs/types\";\r\nimport { GLOBAL_OBJECTS, MUTATING_ARRAY_METHODS } from \"./helpers/globals\";\r\nimport { isInside, rootIdentifier, isWriteOperator } from \"./helpers/ast\";\r\nimport {\r\n isPromiseLike,\r\n isAwaited,\r\n isReturned,\r\n isHandledWithThen,\r\n isAsyncForEach,\r\n isAsyncEventListener,\r\n isPromiseAll,\r\n} from \"./helpers/async\";\r\n\r\nexport function analyzePurity(\r\n program: ts.Program,\r\n functions: FunctionNode[]\r\n): PurityResult[] {\r\n const checker = program.getTypeChecker();\r\n const results: PurityResult[] = [];\r\n\r\n for (const fn of functions) {\r\n const sf = fn.node.getSourceFile();\r\n const { line, character } = sf.getLineAndCharacterOfPosition(\r\n fn.node.getStart(sf)\r\n );\r\n\r\n const reasons: string[] = [];\r\n const paramNames = new Set<string>();\r\n\r\n for (const p of fn.node.parameters) {\r\n if (ts.isIdentifier(p.name)) paramNames.add(p.name.text);\r\n }\r\n\r\n const mark = (reason: string) => {\r\n if (!reasons.includes(reason)) reasons.push(reason);\r\n };\r\n\r\n let containsAwait = false;\r\n let hasTryCatch = false;\r\n\r\n const isAsyncFunction =\r\n fn.node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;\r\n\r\n function visit(node: ts.Node): void {\r\n /* ---------------------------\r\n WRITE DETECTION\r\n ----------------------------*/\r\n if (ts.isBinaryExpression(node) && isWriteOperator(node.operatorToken.kind)) {\r\n const left = node.left as ts.Expression;\r\n const root = rootIdentifier(left);\r\n\r\n if (root && paramNames.has(root.text)) {\r\n mark(`Mutates parameter \\`${root.text}\\``);\r\n }\r\n\r\n if (ts.isIdentifier(left)) {\r\n const sym = checker.getSymbolAtLocation(left);\r\n const decls = sym?.getDeclarations() ?? [];\r\n const declaredInside = decls.some(d => isInside(d, fn.node));\r\n if (sym && decls.length > 0 && !declaredInside) {\r\n mark(`Writes to outer scope \\`${left.text}\\``);\r\n }\r\n }\r\n\r\n if (\r\n ts.isPropertyAccessExpression(left) &&\r\n left.expression.kind === ts.SyntaxKind.ThisKeyword\r\n ) {\r\n mark(`Mutates \\`this.${left.name.text}\\``);\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n ARRAY PARAM MUTATION\r\n ----------------------------*/\r\n if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {\r\n const method = node.expression.name.text;\r\n const root = rootIdentifier(node.expression.expression);\r\n\r\n if (root && paramNames.has(root.text) && MUTATING_ARRAY_METHODS.has(method)) {\r\n mark(`Mutates parameter \\`${root.text}\\` via \\`${method}()\\``);\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n GLOBAL ACCESS\r\n ----------------------------*/\r\n if (ts.isIdentifier(node)) {\r\n const sym = checker.getSymbolAtLocation(node);\r\n if (sym && GLOBAL_OBJECTS.has(sym.getName())) {\r\n mark(`Accesses global \\`${sym.getName()}\\``);\r\n }\r\n }\r\n\r\n if (ts.isPropertyAccessExpression(node)) {\r\n const root = rootIdentifier(node.expression);\r\n\r\n if (root?.text === \"process\" && node.name.text === \"env\") {\r\n mark(\"Accesses `process.env` (non-deterministic)\");\r\n }\r\n\r\n if (root?.text === \"console\") {\r\n mark(\"Writes to console\");\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n CALL EXPRESSIONS\r\n ----------------------------*/\r\n if (ts.isCallExpression(node)) {\r\n const type = checker.getTypeAtLocation(node);\r\n const expr = node.expression;\r\n\r\n // Non-deterministic\r\n if (\r\n ts.isPropertyAccessExpression(expr) &&\r\n ts.isIdentifier(expr.expression) &&\r\n expr.expression.text === \"Date\" &&\r\n expr.name.text === \"now\"\r\n ) {\r\n mark(\"Calls Date.now() (non-deterministic)\");\r\n }\r\n\r\n if (\r\n ts.isPropertyAccessExpression(expr) &&\r\n ts.isIdentifier(expr.expression) &&\r\n expr.expression.text === \"Math\" &&\r\n expr.name.text === \"random\"\r\n ) {\r\n mark(\"Calls Math.random() (non-deterministic)\");\r\n }\r\n\r\n // Async rules\r\n if (isPromiseLike(type, checker)) {\r\n if (!isAwaited(node) && !isReturned(node) && !isHandledWithThen(node)) {\r\n mark(\"Floating Promise (result ignored)\");\r\n }\r\n }\r\n\r\n if (isPromiseAll(node) && !isAwaited(node) && !isReturned(node)) {\r\n mark(\"Unawaited Promise.all()\");\r\n }\r\n\r\n if (isAsyncForEach(node)) {\r\n mark(\"Async callback inside forEach (not awaited)\");\r\n }\r\n\r\n if (isAsyncEventListener(node)) {\r\n mark(\"Async event listener without error boundary\");\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n NEW EXPRESSIONS\r\n ----------------------------*/\r\n if (ts.isNewExpression(node)) {\r\n if (ts.isIdentifier(node.expression) && node.expression.text === \"Date\") {\r\n mark(\"Instantiates Date (non-deterministic)\");\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n AWAIT + ERROR HANDLING\r\n ----------------------------*/\r\n if (ts.isAwaitExpression(node)) {\r\n containsAwait = true;\r\n }\r\n\r\n if (ts.isTryStatement(node) && node.catchClause) {\r\n hasTryCatch = true;\r\n }\r\n\r\n if (ts.isCatchClause(node) && node.block.statements.length === 0) {\r\n mark(\"Empty catch block (error swallowed)\");\r\n }\r\n\r\n ts.forEachChild(node, visit);\r\n }\r\n\r\n visit(fn.node);\r\n\r\n if (isAsyncFunction && !containsAwait) {\r\n mark(\"Async function without await\");\r\n }\r\n\r\n if (isAsyncFunction && containsAwait && !hasTryCatch) {\r\n mark(\"Async function without error handling (no try/catch)\");\r\n }\r\n\r\n results.push({\r\n id: fn.id,\r\n name: fn.name,\r\n file: fn.file,\r\n line: line + 1,\r\n character: character + 1,\r\n isPure: reasons.length === 0,\r\n reasons,\r\n });\r\n }\r\n\r\n return results;\r\n}\r\n","export const GLOBAL_OBJECTS = new Set([\r\n \"window\",\r\n \"document\",\r\n \"global\",\r\n \"process\",\r\n \"localStorage\",\r\n \"sessionStorage\",\r\n]);\r\n\r\nexport const MUTATING_ARRAY_METHODS = new Set([\r\n \"push\",\r\n \"pop\",\r\n \"splice\",\r\n \"shift\",\r\n \"unshift\",\r\n \"sort\",\r\n \"reverse\",\r\n \"copyWithin\",\r\n \"fill\",\r\n]);\r\n","import ts from \"typescript\";\r\n\r\nexport function isInside(node: ts.Node, container: ts.Node) {\r\n return node.pos >= container.pos && node.end <= container.end;\r\n}\r\n\r\nexport function rootIdentifier(expr: ts.Expression): ts.Identifier | null {\r\n if (ts.isIdentifier(expr)) return expr;\r\n if (ts.isPropertyAccessExpression(expr)) return rootIdentifier(expr.expression);\r\n if (ts.isElementAccessExpression(expr)) return rootIdentifier(expr.expression);\r\n if (ts.isParenthesizedExpression(expr)) return rootIdentifier(expr.expression);\r\n return null;\r\n}\r\n\r\nexport function isWriteOperator(kind: ts.SyntaxKind) {\r\n return (\r\n kind === ts.SyntaxKind.EqualsToken ||\r\n kind === ts.SyntaxKind.PlusEqualsToken ||\r\n kind === ts.SyntaxKind.MinusEqualsToken ||\r\n kind === ts.SyntaxKind.AsteriskEqualsToken ||\r\n kind === ts.SyntaxKind.SlashEqualsToken ||\r\n kind === ts.SyntaxKind.PercentEqualsToken ||\r\n kind === ts.SyntaxKind.AmpersandEqualsToken ||\r\n kind === ts.SyntaxKind.BarEqualsToken ||\r\n kind === ts.SyntaxKind.CaretEqualsToken ||\r\n kind === ts.SyntaxKind.LessThanLessThanEqualsToken ||\r\n kind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken ||\r\n kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken\r\n );\r\n}\r\n","import ts from \"typescript\";\r\n\r\nexport function isPromiseLike(type: ts.Type, checker: ts.TypeChecker): boolean {\r\n if (!type) return false;\r\n\r\n const thenProp = type.getProperty(\"then\");\r\n if (!thenProp) return false;\r\n\r\n const decl = thenProp.valueDeclaration ?? thenProp.declarations?.[0];\r\n if (!decl) return false;\r\n\r\n const thenType = checker.getTypeOfSymbolAtLocation(thenProp, decl);\r\n return thenType.getCallSignatures().length > 0;\r\n}\r\n\r\nexport function isAwaited(node: ts.CallExpression) {\r\n return ts.isAwaitExpression(node.parent);\r\n}\r\n\r\nexport function isReturned(node: ts.CallExpression) {\r\n return ts.isReturnStatement(node.parent);\r\n}\r\n\r\nexport function isHandledWithThen(node: ts.CallExpression) {\r\n if (!ts.isPropertyAccessExpression(node.parent)) return false;\r\n const name = node.parent.name.text;\r\n return name === \"then\" || name === \"catch\" || name === \"finally\";\r\n}\r\n\r\nexport function isAsyncForEach(node: ts.CallExpression): boolean {\r\n if (!ts.isPropertyAccessExpression(node.expression)) return false;\r\n if (node.expression.name.text !== \"forEach\") return false;\r\n\r\n const cb = node.arguments[0];\r\n if (!cb) return false;\r\n\r\n if (ts.isArrowFunction(cb) || ts.isFunctionExpression(cb)) {\r\n return cb.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;\r\n }\r\n\r\n return false;\r\n}\r\n\r\nexport function isAsyncEventListener(node: ts.CallExpression): boolean {\r\n if (!ts.isPropertyAccessExpression(node.expression)) return false;\r\n if (node.expression.name.text !== \"addEventListener\") return false;\r\n\r\n const cb = node.arguments[1];\r\n if (!cb) return false;\r\n\r\n if (ts.isArrowFunction(cb) || ts.isFunctionExpression(cb)) {\r\n return cb.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;\r\n }\r\n\r\n return false;\r\n}\r\n\r\nexport function isPromiseAll(node: ts.CallExpression): boolean {\r\n return (\r\n ts.isPropertyAccessExpression(node.expression) &&\r\n ts.isIdentifier(node.expression.expression) &&\r\n node.expression.expression.text === \"Promise\" &&\r\n node.expression.name.text === \"all\"\r\n );\r\n}\r\n","import { CallGraph } from \"./buildCallGraph\";\r\nimport { PurityResult } from \"../../libs/types\";\r\n\r\nexport function propagateImpurity(\r\n graph: CallGraph,\r\n purity: PurityResult[]\r\n): PurityResult[] {\r\n\r\n // Map functionId -> PurityResult\r\n const resultMap = new Map<string, PurityResult>();\r\n for (const r of purity) {\r\n resultMap.set(r.id, r);\r\n }\r\n\r\n // Build reverse graph: calleeId -> Set<callerId>\r\n const reverse = new Map<string, Set<string>>();\r\n\r\n for (const [callerId, calleeIds] of graph.entries()) {\r\n for (const calleeId of calleeIds) {\r\n if (!reverse.has(calleeId)) {\r\n reverse.set(calleeId, new Set());\r\n }\r\n reverse.get(calleeId)!.add(callerId);\r\n }\r\n }\r\n\r\n // BFS propagation\r\n const queue: string[] = [];\r\n\r\n // Seed with initially impure functions\r\n for (const r of purity) {\r\n if (!r.isPure) {\r\n queue.push(r.id);\r\n }\r\n }\r\n\r\n while (queue.length > 0) {\r\n const impureId = queue.shift()!;\r\n const callers = reverse.get(impureId);\r\n if (!callers) continue;\r\n\r\n for (const callerId of callers) {\r\n const caller = resultMap.get(callerId);\r\n if (!caller) continue;\r\n\r\n if (caller.isPure) {\r\n caller.isPure = false;\r\n caller.reasons.push(`Calls impure function`);\r\n queue.push(callerId);\r\n }\r\n }\r\n }\r\n\r\n return Array.from(resultMap.values());\r\n}\r\n","#!/usr/bin/env node\r\nimport { createTsProgram } from \"./analyzer/createProgram\";\r\nimport { collectFunctions } from \"./analyzer/collectFunctions\";\r\nimport { buildCallGraph } from \"./analyzer/buildCallGraph\";\r\nimport { analyzePurity } from \"./analyzer/purity\";\r\nimport { propagateImpurity } from \"./analyzer/propagateImpurity\";\r\n\r\nconst command = process.argv[2];\r\n\r\nif (command !== \"analyze\") {\r\n console.log(\"Usage: callograph analyze\");\r\n process.exit(1);\r\n}\r\n\r\nconst program = createTsProgram(\"tsconfig.json\");\r\nconst functions = collectFunctions(program);\r\nconst graph = buildCallGraph(program, functions);\r\n\r\nlet purity = analyzePurity(program, functions);\r\npurity = propagateImpurity(graph, purity);\r\n\r\n/* ------------------------------\r\n CALL GRAPH\r\n--------------------------------*/\r\nconsole.log(\"\\nCall Graph:\\n\");\r\n\r\nlet edgeCount = 0;\r\n\r\nfor (const [callerId, callees] of graph.entries()) {\r\n const caller = functions.find(f => f.id === callerId);\r\n const callerName = caller ? caller.name : callerId;\r\n\r\n for (const calleeId of callees) {\r\n const callee = functions.find(f => f.id === calleeId);\r\n const calleeName = callee ? callee.name : calleeId;\r\n\r\n console.log(`${callerName} → ${calleeName}`);\r\n edgeCount++;\r\n }\r\n}\r\n\r\nif (edgeCount === 0) {\r\n console.log(\"(no project-level call edges found)\");\r\n}\r\n\r\nconsole.log(`\\nTotal call edges: ${edgeCount}`);\r\n\r\n/* ------------------------------\r\n PURITY REPORT\r\n--------------------------------*/\r\nconsole.log(\"\\nPurity Report:\\n\");\r\n\r\nconst impure = purity.filter(r => !r.isPure);\r\n\r\nif (impure.length === 0) {\r\n console.log(\"All analyzed functions are pure.\");\r\n} else {\r\n for (const r of impure) {\r\n console.log(`${r.file}:${r.line}:${r.character}`);\r\n console.log(`Function: ${r.name}`);\r\n console.log(`Status: X Impure`);\r\n console.log(\"Reasons:\");\r\n for (const reason of r.reasons) {\r\n console.log(` - ${reason}`);\r\n }\r\n console.log(\"\");\r\n }\r\n}\r\n"],"mappings":"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","names":["ts","import_typescript","ts","program","import_typescript","program","functions","graph","visit","ts","import_typescript","import_typescript","ts","import_typescript","ts","program","functions","visit","ts","graph","purity"]}
package/dist/cli.d.cts CHANGED
@@ -1,2 +1 @@
1
-
2
- export { }
1
+ #!/usr/bin/env node
package/dist/cli.d.ts CHANGED
@@ -1,2 +1 @@
1
-
2
- export { }
1
+ #!/usr/bin/env node
package/dist/cli.js CHANGED
@@ -1,3 +1,6 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+
1
4
  // src/analyzer/createProgram.ts
2
5
  import ts from "typescript";
3
6
  function createTsProgram(tsconfigPath) {
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/analyzer/createProgram.ts","../src/analyzer/collectFunctions.ts","../src/analyzer/buildCallGraph.ts","../src/analyzer/purity.ts","../src/analyzer/helpers/globals.ts","../src/analyzer/helpers/ast.ts","../src/analyzer/helpers/async.ts","../src/analyzer/propagateImpurity.ts","../src/cli.ts"],"sourcesContent":["import ts from \"typescript\";\r\n\r\nexport function createTsProgram(tsconfigPath: string) {\r\n const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\r\n\r\n if (configFile.error) {\r\n throw new Error(\r\n ts.flattenDiagnosticMessageText(configFile.error.messageText, \"\\n\")\r\n );\r\n }\r\n\r\n const parsedConfig = ts.parseJsonConfigFileContent(\r\n configFile.config,\r\n ts.sys,\r\n process.cwd()\r\n );\r\n\r\n return ts.createProgram({\r\n rootNames: parsedConfig.fileNames,\r\n options: parsedConfig.options,\r\n });\r\n}\r\n","import ts from \"typescript\";\r\nimport { FunctionNode } from \"../../libs/types\";\r\n\r\nfunction safeName(node: ts.FunctionLikeDeclaration, sf: ts.SourceFile): string {\r\n const anyNode = node as any;\r\n\r\n // Named declarations / methods: function foo() {} / class X { foo() {} }\r\n if (anyNode.name && ts.isIdentifier(anyNode.name)) return anyNode.name.text;\r\n\r\n // Methods with string/numeric names: class X { \"foo\"() {} }\r\n if (\r\n anyNode.name &&\r\n (ts.isStringLiteral(anyNode.name) || ts.isNumericLiteral(anyNode.name))\r\n ) {\r\n return String(anyNode.name.text);\r\n }\r\n\r\n // const foo = () => {} / const foo = function() {}\r\n const p = node.parent;\r\n if (p && ts.isVariableDeclaration(p) && ts.isIdentifier(p.name)) return p.name.text;\r\n\r\n // obj.foo = function() {}\r\n if (p && ts.isBinaryExpression(p) && ts.isPropertyAccessExpression(p.left)) {\r\n return p.left.name.text;\r\n }\r\n\r\n const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf));\r\n return `<anonymous@${line + 1}:${character + 1}>`;\r\n}\r\n\r\nexport function collectFunctions(program: ts.Program): FunctionNode[] {\r\n const out: FunctionNode[] = [];\r\n\r\n for (const sf of program.getSourceFiles()) {\r\n if (sf.isDeclarationFile) continue;\r\n if (sf.fileName.includes(\"node_modules\")) continue;\r\n\r\n ts.forEachChild(sf, function visit(n) {\r\n const isNamedFunctionDecl = ts.isFunctionDeclaration(n) && !!n.name;\r\n const isMethod = ts.isMethodDeclaration(n);\r\n\r\n const p = n.parent; //guard parent\r\n const isVarAssigned =\r\n (ts.isArrowFunction(n) || ts.isFunctionExpression(n)) &&\r\n !!p &&\r\n ts.isVariableDeclaration(p) &&\r\n ts.isIdentifier(p.name);\r\n\r\n if (isNamedFunctionDecl || isMethod || isVarAssigned) {\r\n const name = safeName(n, sf);\r\n const id = `${sf.fileName}:${n.pos}:${name}`;\r\n out.push({ id, name, file: sf.fileName, node: n });\r\n }\r\n\r\n ts.forEachChild(n, visit);\r\n });\r\n }\r\n\r\n return out;\r\n}\r\n","import ts from \"typescript\";\r\nimport { FunctionNode } from \"../../libs/types\";\r\n\r\n// callerId -> calleeIds\r\nexport type CallGraph = Map<string, Set<string>>;\r\n\r\nfunction isProjectFile(fileName: string) {\r\n return !fileName.includes(\"node_modules\") && !fileName.endsWith(\".d.ts\");\r\n}\r\n\r\nexport function buildCallGraph(\r\n program: ts.Program,\r\n functions: FunctionNode[]\r\n): CallGraph {\r\n\r\n const checker = program.getTypeChecker();\r\n const graph: CallGraph = new Map();\r\n\r\n const declToId = new Map<ts.Declaration, string>();\r\n\r\n for (const fn of functions) {\r\n graph.set(fn.id, new Set());\r\n declToId.set(fn.node, fn.id);\r\n }\r\n\r\n for (const fn of functions) {\r\n const callerId = fn.id;\r\n\r\n function visit(node: ts.Node) {\r\n if (ts.isCallExpression(node)) {\r\n const sig = checker.getResolvedSignature(node);\r\n const decl = sig?.getDeclaration();\r\n\r\n if (\r\n decl &&\r\n isProjectFile(decl.getSourceFile().fileName)\r\n ) {\r\n const calleeId = declToId.get(decl as ts.Declaration);\r\n\r\n if (calleeId) {\r\n graph.get(callerId)!.add(calleeId);\r\n }\r\n }\r\n }\r\n\r\n ts.forEachChild(node, visit);\r\n }\r\n\r\n visit(fn.node);\r\n }\r\n\r\n return graph;\r\n}\r\n","import ts from \"typescript\";\r\nimport { FunctionNode, PurityResult } from \"../../libs/types\";\r\nimport { GLOBAL_OBJECTS, MUTATING_ARRAY_METHODS } from \"./helpers/globals\";\r\nimport { isInside, rootIdentifier, isWriteOperator } from \"./helpers/ast\";\r\nimport {\r\n isPromiseLike,\r\n isAwaited,\r\n isReturned,\r\n isHandledWithThen,\r\n isAsyncForEach,\r\n isAsyncEventListener,\r\n isPromiseAll,\r\n} from \"./helpers/async\";\r\n\r\nexport function analyzePurity(\r\n program: ts.Program,\r\n functions: FunctionNode[]\r\n): PurityResult[] {\r\n const checker = program.getTypeChecker();\r\n const results: PurityResult[] = [];\r\n\r\n for (const fn of functions) {\r\n const sf = fn.node.getSourceFile();\r\n const { line, character } = sf.getLineAndCharacterOfPosition(\r\n fn.node.getStart(sf)\r\n );\r\n\r\n const reasons: string[] = [];\r\n const paramNames = new Set<string>();\r\n\r\n for (const p of fn.node.parameters) {\r\n if (ts.isIdentifier(p.name)) paramNames.add(p.name.text);\r\n }\r\n\r\n const mark = (reason: string) => {\r\n if (!reasons.includes(reason)) reasons.push(reason);\r\n };\r\n\r\n let containsAwait = false;\r\n let hasTryCatch = false;\r\n\r\n const isAsyncFunction =\r\n fn.node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;\r\n\r\n function visit(node: ts.Node): void {\r\n /* ---------------------------\r\n WRITE DETECTION\r\n ----------------------------*/\r\n if (ts.isBinaryExpression(node) && isWriteOperator(node.operatorToken.kind)) {\r\n const left = node.left as ts.Expression;\r\n const root = rootIdentifier(left);\r\n\r\n if (root && paramNames.has(root.text)) {\r\n mark(`Mutates parameter \\`${root.text}\\``);\r\n }\r\n\r\n if (ts.isIdentifier(left)) {\r\n const sym = checker.getSymbolAtLocation(left);\r\n const decls = sym?.getDeclarations() ?? [];\r\n const declaredInside = decls.some(d => isInside(d, fn.node));\r\n if (sym && decls.length > 0 && !declaredInside) {\r\n mark(`Writes to outer scope \\`${left.text}\\``);\r\n }\r\n }\r\n\r\n if (\r\n ts.isPropertyAccessExpression(left) &&\r\n left.expression.kind === ts.SyntaxKind.ThisKeyword\r\n ) {\r\n mark(`Mutates \\`this.${left.name.text}\\``);\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n ARRAY PARAM MUTATION\r\n ----------------------------*/\r\n if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {\r\n const method = node.expression.name.text;\r\n const root = rootIdentifier(node.expression.expression);\r\n\r\n if (root && paramNames.has(root.text) && MUTATING_ARRAY_METHODS.has(method)) {\r\n mark(`Mutates parameter \\`${root.text}\\` via \\`${method}()\\``);\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n GLOBAL ACCESS\r\n ----------------------------*/\r\n if (ts.isIdentifier(node)) {\r\n const sym = checker.getSymbolAtLocation(node);\r\n if (sym && GLOBAL_OBJECTS.has(sym.getName())) {\r\n mark(`Accesses global \\`${sym.getName()}\\``);\r\n }\r\n }\r\n\r\n if (ts.isPropertyAccessExpression(node)) {\r\n const root = rootIdentifier(node.expression);\r\n\r\n if (root?.text === \"process\" && node.name.text === \"env\") {\r\n mark(\"Accesses `process.env` (non-deterministic)\");\r\n }\r\n\r\n if (root?.text === \"console\") {\r\n mark(\"Writes to console\");\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n CALL EXPRESSIONS\r\n ----------------------------*/\r\n if (ts.isCallExpression(node)) {\r\n const type = checker.getTypeAtLocation(node);\r\n const expr = node.expression;\r\n\r\n // Non-deterministic\r\n if (\r\n ts.isPropertyAccessExpression(expr) &&\r\n ts.isIdentifier(expr.expression) &&\r\n expr.expression.text === \"Date\" &&\r\n expr.name.text === \"now\"\r\n ) {\r\n mark(\"Calls Date.now() (non-deterministic)\");\r\n }\r\n\r\n if (\r\n ts.isPropertyAccessExpression(expr) &&\r\n ts.isIdentifier(expr.expression) &&\r\n expr.expression.text === \"Math\" &&\r\n expr.name.text === \"random\"\r\n ) {\r\n mark(\"Calls Math.random() (non-deterministic)\");\r\n }\r\n\r\n // Async rules\r\n if (isPromiseLike(type, checker)) {\r\n if (!isAwaited(node) && !isReturned(node) && !isHandledWithThen(node)) {\r\n mark(\"Floating Promise (result ignored)\");\r\n }\r\n }\r\n\r\n if (isPromiseAll(node) && !isAwaited(node) && !isReturned(node)) {\r\n mark(\"Unawaited Promise.all()\");\r\n }\r\n\r\n if (isAsyncForEach(node)) {\r\n mark(\"Async callback inside forEach (not awaited)\");\r\n }\r\n\r\n if (isAsyncEventListener(node)) {\r\n mark(\"Async event listener without error boundary\");\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n NEW EXPRESSIONS\r\n ----------------------------*/\r\n if (ts.isNewExpression(node)) {\r\n if (ts.isIdentifier(node.expression) && node.expression.text === \"Date\") {\r\n mark(\"Instantiates Date (non-deterministic)\");\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n AWAIT + ERROR HANDLING\r\n ----------------------------*/\r\n if (ts.isAwaitExpression(node)) {\r\n containsAwait = true;\r\n }\r\n\r\n if (ts.isTryStatement(node) && node.catchClause) {\r\n hasTryCatch = true;\r\n }\r\n\r\n if (ts.isCatchClause(node) && node.block.statements.length === 0) {\r\n mark(\"Empty catch block (error swallowed)\");\r\n }\r\n\r\n ts.forEachChild(node, visit);\r\n }\r\n\r\n visit(fn.node);\r\n\r\n if (isAsyncFunction && !containsAwait) {\r\n mark(\"Async function without await\");\r\n }\r\n\r\n if (isAsyncFunction && containsAwait && !hasTryCatch) {\r\n mark(\"Async function without error handling (no try/catch)\");\r\n }\r\n\r\n results.push({\r\n id: fn.id,\r\n name: fn.name,\r\n file: fn.file,\r\n line: line + 1,\r\n character: character + 1,\r\n isPure: reasons.length === 0,\r\n reasons,\r\n });\r\n }\r\n\r\n return results;\r\n}\r\n","export const GLOBAL_OBJECTS = new Set([\r\n \"window\",\r\n \"document\",\r\n \"global\",\r\n \"process\",\r\n \"localStorage\",\r\n \"sessionStorage\",\r\n]);\r\n\r\nexport const MUTATING_ARRAY_METHODS = new Set([\r\n \"push\",\r\n \"pop\",\r\n \"splice\",\r\n \"shift\",\r\n \"unshift\",\r\n \"sort\",\r\n \"reverse\",\r\n \"copyWithin\",\r\n \"fill\",\r\n]);\r\n","import ts from \"typescript\";\r\n\r\nexport function isInside(node: ts.Node, container: ts.Node) {\r\n return node.pos >= container.pos && node.end <= container.end;\r\n}\r\n\r\nexport function rootIdentifier(expr: ts.Expression): ts.Identifier | null {\r\n if (ts.isIdentifier(expr)) return expr;\r\n if (ts.isPropertyAccessExpression(expr)) return rootIdentifier(expr.expression);\r\n if (ts.isElementAccessExpression(expr)) return rootIdentifier(expr.expression);\r\n if (ts.isParenthesizedExpression(expr)) return rootIdentifier(expr.expression);\r\n return null;\r\n}\r\n\r\nexport function isWriteOperator(kind: ts.SyntaxKind) {\r\n return (\r\n kind === ts.SyntaxKind.EqualsToken ||\r\n kind === ts.SyntaxKind.PlusEqualsToken ||\r\n kind === ts.SyntaxKind.MinusEqualsToken ||\r\n kind === ts.SyntaxKind.AsteriskEqualsToken ||\r\n kind === ts.SyntaxKind.SlashEqualsToken ||\r\n kind === ts.SyntaxKind.PercentEqualsToken ||\r\n kind === ts.SyntaxKind.AmpersandEqualsToken ||\r\n kind === ts.SyntaxKind.BarEqualsToken ||\r\n kind === ts.SyntaxKind.CaretEqualsToken ||\r\n kind === ts.SyntaxKind.LessThanLessThanEqualsToken ||\r\n kind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken ||\r\n kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken\r\n );\r\n}\r\n","import ts from \"typescript\";\r\n\r\nexport function isPromiseLike(type: ts.Type, checker: ts.TypeChecker): boolean {\r\n if (!type) return false;\r\n\r\n const thenProp = type.getProperty(\"then\");\r\n if (!thenProp) return false;\r\n\r\n const decl = thenProp.valueDeclaration ?? thenProp.declarations?.[0];\r\n if (!decl) return false;\r\n\r\n const thenType = checker.getTypeOfSymbolAtLocation(thenProp, decl);\r\n return thenType.getCallSignatures().length > 0;\r\n}\r\n\r\nexport function isAwaited(node: ts.CallExpression) {\r\n return ts.isAwaitExpression(node.parent);\r\n}\r\n\r\nexport function isReturned(node: ts.CallExpression) {\r\n return ts.isReturnStatement(node.parent);\r\n}\r\n\r\nexport function isHandledWithThen(node: ts.CallExpression) {\r\n if (!ts.isPropertyAccessExpression(node.parent)) return false;\r\n const name = node.parent.name.text;\r\n return name === \"then\" || name === \"catch\" || name === \"finally\";\r\n}\r\n\r\nexport function isAsyncForEach(node: ts.CallExpression): boolean {\r\n if (!ts.isPropertyAccessExpression(node.expression)) return false;\r\n if (node.expression.name.text !== \"forEach\") return false;\r\n\r\n const cb = node.arguments[0];\r\n if (!cb) return false;\r\n\r\n if (ts.isArrowFunction(cb) || ts.isFunctionExpression(cb)) {\r\n return cb.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;\r\n }\r\n\r\n return false;\r\n}\r\n\r\nexport function isAsyncEventListener(node: ts.CallExpression): boolean {\r\n if (!ts.isPropertyAccessExpression(node.expression)) return false;\r\n if (node.expression.name.text !== \"addEventListener\") return false;\r\n\r\n const cb = node.arguments[1];\r\n if (!cb) return false;\r\n\r\n if (ts.isArrowFunction(cb) || ts.isFunctionExpression(cb)) {\r\n return cb.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;\r\n }\r\n\r\n return false;\r\n}\r\n\r\nexport function isPromiseAll(node: ts.CallExpression): boolean {\r\n return (\r\n ts.isPropertyAccessExpression(node.expression) &&\r\n ts.isIdentifier(node.expression.expression) &&\r\n node.expression.expression.text === \"Promise\" &&\r\n node.expression.name.text === \"all\"\r\n );\r\n}\r\n","import { CallGraph } from \"./buildCallGraph\";\r\nimport { PurityResult } from \"../../libs/types\";\r\n\r\nexport function propagateImpurity(\r\n graph: CallGraph,\r\n purity: PurityResult[]\r\n): PurityResult[] {\r\n\r\n // Map functionId -> PurityResult\r\n const resultMap = new Map<string, PurityResult>();\r\n for (const r of purity) {\r\n resultMap.set(r.id, r);\r\n }\r\n\r\n // Build reverse graph: calleeId -> Set<callerId>\r\n const reverse = new Map<string, Set<string>>();\r\n\r\n for (const [callerId, calleeIds] of graph.entries()) {\r\n for (const calleeId of calleeIds) {\r\n if (!reverse.has(calleeId)) {\r\n reverse.set(calleeId, new Set());\r\n }\r\n reverse.get(calleeId)!.add(callerId);\r\n }\r\n }\r\n\r\n // BFS propagation\r\n const queue: string[] = [];\r\n\r\n // Seed with initially impure functions\r\n for (const r of purity) {\r\n if (!r.isPure) {\r\n queue.push(r.id);\r\n }\r\n }\r\n\r\n while (queue.length > 0) {\r\n const impureId = queue.shift()!;\r\n const callers = reverse.get(impureId);\r\n if (!callers) continue;\r\n\r\n for (const callerId of callers) {\r\n const caller = resultMap.get(callerId);\r\n if (!caller) continue;\r\n\r\n if (caller.isPure) {\r\n caller.isPure = false;\r\n caller.reasons.push(`Calls impure function`);\r\n queue.push(callerId);\r\n }\r\n }\r\n }\r\n\r\n return Array.from(resultMap.values());\r\n}\r\n","import { createTsProgram } from \"./analyzer/createProgram\";\r\nimport { collectFunctions } from \"./analyzer/collectFunctions\";\r\nimport { buildCallGraph } from \"./analyzer/buildCallGraph\";\r\nimport { analyzePurity } from \"./analyzer/purity\";\r\nimport { propagateImpurity } from \"./analyzer/propagateImpurity\";\r\n\r\nconst command = process.argv[2];\r\n\r\nif (command !== \"analyze\") {\r\n console.log(\"Usage: callograph analyze\");\r\n process.exit(1);\r\n}\r\n\r\nconst program = createTsProgram(\"tsconfig.json\");\r\nconst functions = collectFunctions(program);\r\nconst graph = buildCallGraph(program, functions);\r\n\r\nlet purity = analyzePurity(program, functions);\r\npurity = propagateImpurity(graph, purity);\r\n\r\n/* ------------------------------\r\n CALL GRAPH\r\n--------------------------------*/\r\nconsole.log(\"\\nCall Graph:\\n\");\r\n\r\nlet edgeCount = 0;\r\n\r\nfor (const [callerId, callees] of graph.entries()) {\r\n const caller = functions.find(f => f.id === callerId);\r\n const callerName = caller ? caller.name : callerId;\r\n\r\n for (const calleeId of callees) {\r\n const callee = functions.find(f => f.id === calleeId);\r\n const calleeName = callee ? callee.name : calleeId;\r\n\r\n console.log(`${callerName} → ${calleeName}`);\r\n edgeCount++;\r\n }\r\n}\r\n\r\nif (edgeCount === 0) {\r\n console.log(\"(no project-level call edges found)\");\r\n}\r\n\r\nconsole.log(`\\nTotal call edges: ${edgeCount}`);\r\n\r\n/* ------------------------------\r\n PURITY REPORT\r\n--------------------------------*/\r\nconsole.log(\"\\nPurity Report:\\n\");\r\n\r\nconst impure = purity.filter(r => !r.isPure);\r\n\r\nif (impure.length === 0) {\r\n console.log(\"All analyzed functions are pure.\");\r\n} else {\r\n for (const r of impure) {\r\n console.log(`${r.file}:${r.line}:${r.character}`);\r\n console.log(`Function: ${r.name}`);\r\n console.log(`Status: X Impure`);\r\n console.log(\"Reasons:\");\r\n for (const reason of r.reasons) {\r\n console.log(` - ${reason}`);\r\n }\r\n console.log(\"\");\r\n }\r\n}\r\n"],"mappings":"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","names":["ts","program","ts","program","functions","graph","visit","ts","ts","ts","program","functions","visit","ts","graph","purity"]}
1
+ {"version":3,"sources":["../src/analyzer/createProgram.ts","../src/analyzer/collectFunctions.ts","../src/analyzer/buildCallGraph.ts","../src/analyzer/purity.ts","../src/analyzer/helpers/globals.ts","../src/analyzer/helpers/ast.ts","../src/analyzer/helpers/async.ts","../src/analyzer/propagateImpurity.ts","../src/cli.ts"],"sourcesContent":["import ts from \"typescript\";\r\n\r\nexport function createTsProgram(tsconfigPath: string) {\r\n const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);\r\n\r\n if (configFile.error) {\r\n throw new Error(\r\n ts.flattenDiagnosticMessageText(configFile.error.messageText, \"\\n\")\r\n );\r\n }\r\n\r\n const parsedConfig = ts.parseJsonConfigFileContent(\r\n configFile.config,\r\n ts.sys,\r\n process.cwd()\r\n );\r\n\r\n return ts.createProgram({\r\n rootNames: parsedConfig.fileNames,\r\n options: parsedConfig.options,\r\n });\r\n}\r\n","import ts from \"typescript\";\r\nimport { FunctionNode } from \"../../libs/types\";\r\n\r\nfunction safeName(node: ts.FunctionLikeDeclaration, sf: ts.SourceFile): string {\r\n const anyNode = node as any;\r\n\r\n // Named declarations / methods: function foo() {} / class X { foo() {} }\r\n if (anyNode.name && ts.isIdentifier(anyNode.name)) return anyNode.name.text;\r\n\r\n // Methods with string/numeric names: class X { \"foo\"() {} }\r\n if (\r\n anyNode.name &&\r\n (ts.isStringLiteral(anyNode.name) || ts.isNumericLiteral(anyNode.name))\r\n ) {\r\n return String(anyNode.name.text);\r\n }\r\n\r\n // const foo = () => {} / const foo = function() {}\r\n const p = node.parent;\r\n if (p && ts.isVariableDeclaration(p) && ts.isIdentifier(p.name)) return p.name.text;\r\n\r\n // obj.foo = function() {}\r\n if (p && ts.isBinaryExpression(p) && ts.isPropertyAccessExpression(p.left)) {\r\n return p.left.name.text;\r\n }\r\n\r\n const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf));\r\n return `<anonymous@${line + 1}:${character + 1}>`;\r\n}\r\n\r\nexport function collectFunctions(program: ts.Program): FunctionNode[] {\r\n const out: FunctionNode[] = [];\r\n\r\n for (const sf of program.getSourceFiles()) {\r\n if (sf.isDeclarationFile) continue;\r\n if (sf.fileName.includes(\"node_modules\")) continue;\r\n\r\n ts.forEachChild(sf, function visit(n) {\r\n const isNamedFunctionDecl = ts.isFunctionDeclaration(n) && !!n.name;\r\n const isMethod = ts.isMethodDeclaration(n);\r\n\r\n const p = n.parent; //guard parent\r\n const isVarAssigned =\r\n (ts.isArrowFunction(n) || ts.isFunctionExpression(n)) &&\r\n !!p &&\r\n ts.isVariableDeclaration(p) &&\r\n ts.isIdentifier(p.name);\r\n\r\n if (isNamedFunctionDecl || isMethod || isVarAssigned) {\r\n const name = safeName(n, sf);\r\n const id = `${sf.fileName}:${n.pos}:${name}`;\r\n out.push({ id, name, file: sf.fileName, node: n });\r\n }\r\n\r\n ts.forEachChild(n, visit);\r\n });\r\n }\r\n\r\n return out;\r\n}\r\n","import ts from \"typescript\";\r\nimport { FunctionNode } from \"../../libs/types\";\r\n\r\n// callerId -> calleeIds\r\nexport type CallGraph = Map<string, Set<string>>;\r\n\r\nfunction isProjectFile(fileName: string) {\r\n return !fileName.includes(\"node_modules\") && !fileName.endsWith(\".d.ts\");\r\n}\r\n\r\nexport function buildCallGraph(\r\n program: ts.Program,\r\n functions: FunctionNode[]\r\n): CallGraph {\r\n\r\n const checker = program.getTypeChecker();\r\n const graph: CallGraph = new Map();\r\n\r\n const declToId = new Map<ts.Declaration, string>();\r\n\r\n for (const fn of functions) {\r\n graph.set(fn.id, new Set());\r\n declToId.set(fn.node, fn.id);\r\n }\r\n\r\n for (const fn of functions) {\r\n const callerId = fn.id;\r\n\r\n function visit(node: ts.Node) {\r\n if (ts.isCallExpression(node)) {\r\n const sig = checker.getResolvedSignature(node);\r\n const decl = sig?.getDeclaration();\r\n\r\n if (\r\n decl &&\r\n isProjectFile(decl.getSourceFile().fileName)\r\n ) {\r\n const calleeId = declToId.get(decl as ts.Declaration);\r\n\r\n if (calleeId) {\r\n graph.get(callerId)!.add(calleeId);\r\n }\r\n }\r\n }\r\n\r\n ts.forEachChild(node, visit);\r\n }\r\n\r\n visit(fn.node);\r\n }\r\n\r\n return graph;\r\n}\r\n","import ts from \"typescript\";\r\nimport { FunctionNode, PurityResult } from \"../../libs/types\";\r\nimport { GLOBAL_OBJECTS, MUTATING_ARRAY_METHODS } from \"./helpers/globals\";\r\nimport { isInside, rootIdentifier, isWriteOperator } from \"./helpers/ast\";\r\nimport {\r\n isPromiseLike,\r\n isAwaited,\r\n isReturned,\r\n isHandledWithThen,\r\n isAsyncForEach,\r\n isAsyncEventListener,\r\n isPromiseAll,\r\n} from \"./helpers/async\";\r\n\r\nexport function analyzePurity(\r\n program: ts.Program,\r\n functions: FunctionNode[]\r\n): PurityResult[] {\r\n const checker = program.getTypeChecker();\r\n const results: PurityResult[] = [];\r\n\r\n for (const fn of functions) {\r\n const sf = fn.node.getSourceFile();\r\n const { line, character } = sf.getLineAndCharacterOfPosition(\r\n fn.node.getStart(sf)\r\n );\r\n\r\n const reasons: string[] = [];\r\n const paramNames = new Set<string>();\r\n\r\n for (const p of fn.node.parameters) {\r\n if (ts.isIdentifier(p.name)) paramNames.add(p.name.text);\r\n }\r\n\r\n const mark = (reason: string) => {\r\n if (!reasons.includes(reason)) reasons.push(reason);\r\n };\r\n\r\n let containsAwait = false;\r\n let hasTryCatch = false;\r\n\r\n const isAsyncFunction =\r\n fn.node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;\r\n\r\n function visit(node: ts.Node): void {\r\n /* ---------------------------\r\n WRITE DETECTION\r\n ----------------------------*/\r\n if (ts.isBinaryExpression(node) && isWriteOperator(node.operatorToken.kind)) {\r\n const left = node.left as ts.Expression;\r\n const root = rootIdentifier(left);\r\n\r\n if (root && paramNames.has(root.text)) {\r\n mark(`Mutates parameter \\`${root.text}\\``);\r\n }\r\n\r\n if (ts.isIdentifier(left)) {\r\n const sym = checker.getSymbolAtLocation(left);\r\n const decls = sym?.getDeclarations() ?? [];\r\n const declaredInside = decls.some(d => isInside(d, fn.node));\r\n if (sym && decls.length > 0 && !declaredInside) {\r\n mark(`Writes to outer scope \\`${left.text}\\``);\r\n }\r\n }\r\n\r\n if (\r\n ts.isPropertyAccessExpression(left) &&\r\n left.expression.kind === ts.SyntaxKind.ThisKeyword\r\n ) {\r\n mark(`Mutates \\`this.${left.name.text}\\``);\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n ARRAY PARAM MUTATION\r\n ----------------------------*/\r\n if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {\r\n const method = node.expression.name.text;\r\n const root = rootIdentifier(node.expression.expression);\r\n\r\n if (root && paramNames.has(root.text) && MUTATING_ARRAY_METHODS.has(method)) {\r\n mark(`Mutates parameter \\`${root.text}\\` via \\`${method}()\\``);\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n GLOBAL ACCESS\r\n ----------------------------*/\r\n if (ts.isIdentifier(node)) {\r\n const sym = checker.getSymbolAtLocation(node);\r\n if (sym && GLOBAL_OBJECTS.has(sym.getName())) {\r\n mark(`Accesses global \\`${sym.getName()}\\``);\r\n }\r\n }\r\n\r\n if (ts.isPropertyAccessExpression(node)) {\r\n const root = rootIdentifier(node.expression);\r\n\r\n if (root?.text === \"process\" && node.name.text === \"env\") {\r\n mark(\"Accesses `process.env` (non-deterministic)\");\r\n }\r\n\r\n if (root?.text === \"console\") {\r\n mark(\"Writes to console\");\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n CALL EXPRESSIONS\r\n ----------------------------*/\r\n if (ts.isCallExpression(node)) {\r\n const type = checker.getTypeAtLocation(node);\r\n const expr = node.expression;\r\n\r\n // Non-deterministic\r\n if (\r\n ts.isPropertyAccessExpression(expr) &&\r\n ts.isIdentifier(expr.expression) &&\r\n expr.expression.text === \"Date\" &&\r\n expr.name.text === \"now\"\r\n ) {\r\n mark(\"Calls Date.now() (non-deterministic)\");\r\n }\r\n\r\n if (\r\n ts.isPropertyAccessExpression(expr) &&\r\n ts.isIdentifier(expr.expression) &&\r\n expr.expression.text === \"Math\" &&\r\n expr.name.text === \"random\"\r\n ) {\r\n mark(\"Calls Math.random() (non-deterministic)\");\r\n }\r\n\r\n // Async rules\r\n if (isPromiseLike(type, checker)) {\r\n if (!isAwaited(node) && !isReturned(node) && !isHandledWithThen(node)) {\r\n mark(\"Floating Promise (result ignored)\");\r\n }\r\n }\r\n\r\n if (isPromiseAll(node) && !isAwaited(node) && !isReturned(node)) {\r\n mark(\"Unawaited Promise.all()\");\r\n }\r\n\r\n if (isAsyncForEach(node)) {\r\n mark(\"Async callback inside forEach (not awaited)\");\r\n }\r\n\r\n if (isAsyncEventListener(node)) {\r\n mark(\"Async event listener without error boundary\");\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n NEW EXPRESSIONS\r\n ----------------------------*/\r\n if (ts.isNewExpression(node)) {\r\n if (ts.isIdentifier(node.expression) && node.expression.text === \"Date\") {\r\n mark(\"Instantiates Date (non-deterministic)\");\r\n }\r\n }\r\n\r\n /* ---------------------------\r\n AWAIT + ERROR HANDLING\r\n ----------------------------*/\r\n if (ts.isAwaitExpression(node)) {\r\n containsAwait = true;\r\n }\r\n\r\n if (ts.isTryStatement(node) && node.catchClause) {\r\n hasTryCatch = true;\r\n }\r\n\r\n if (ts.isCatchClause(node) && node.block.statements.length === 0) {\r\n mark(\"Empty catch block (error swallowed)\");\r\n }\r\n\r\n ts.forEachChild(node, visit);\r\n }\r\n\r\n visit(fn.node);\r\n\r\n if (isAsyncFunction && !containsAwait) {\r\n mark(\"Async function without await\");\r\n }\r\n\r\n if (isAsyncFunction && containsAwait && !hasTryCatch) {\r\n mark(\"Async function without error handling (no try/catch)\");\r\n }\r\n\r\n results.push({\r\n id: fn.id,\r\n name: fn.name,\r\n file: fn.file,\r\n line: line + 1,\r\n character: character + 1,\r\n isPure: reasons.length === 0,\r\n reasons,\r\n });\r\n }\r\n\r\n return results;\r\n}\r\n","export const GLOBAL_OBJECTS = new Set([\r\n \"window\",\r\n \"document\",\r\n \"global\",\r\n \"process\",\r\n \"localStorage\",\r\n \"sessionStorage\",\r\n]);\r\n\r\nexport const MUTATING_ARRAY_METHODS = new Set([\r\n \"push\",\r\n \"pop\",\r\n \"splice\",\r\n \"shift\",\r\n \"unshift\",\r\n \"sort\",\r\n \"reverse\",\r\n \"copyWithin\",\r\n \"fill\",\r\n]);\r\n","import ts from \"typescript\";\r\n\r\nexport function isInside(node: ts.Node, container: ts.Node) {\r\n return node.pos >= container.pos && node.end <= container.end;\r\n}\r\n\r\nexport function rootIdentifier(expr: ts.Expression): ts.Identifier | null {\r\n if (ts.isIdentifier(expr)) return expr;\r\n if (ts.isPropertyAccessExpression(expr)) return rootIdentifier(expr.expression);\r\n if (ts.isElementAccessExpression(expr)) return rootIdentifier(expr.expression);\r\n if (ts.isParenthesizedExpression(expr)) return rootIdentifier(expr.expression);\r\n return null;\r\n}\r\n\r\nexport function isWriteOperator(kind: ts.SyntaxKind) {\r\n return (\r\n kind === ts.SyntaxKind.EqualsToken ||\r\n kind === ts.SyntaxKind.PlusEqualsToken ||\r\n kind === ts.SyntaxKind.MinusEqualsToken ||\r\n kind === ts.SyntaxKind.AsteriskEqualsToken ||\r\n kind === ts.SyntaxKind.SlashEqualsToken ||\r\n kind === ts.SyntaxKind.PercentEqualsToken ||\r\n kind === ts.SyntaxKind.AmpersandEqualsToken ||\r\n kind === ts.SyntaxKind.BarEqualsToken ||\r\n kind === ts.SyntaxKind.CaretEqualsToken ||\r\n kind === ts.SyntaxKind.LessThanLessThanEqualsToken ||\r\n kind === ts.SyntaxKind.GreaterThanGreaterThanEqualsToken ||\r\n kind === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken\r\n );\r\n}\r\n","import ts from \"typescript\";\r\n\r\nexport function isPromiseLike(type: ts.Type, checker: ts.TypeChecker): boolean {\r\n if (!type) return false;\r\n\r\n const thenProp = type.getProperty(\"then\");\r\n if (!thenProp) return false;\r\n\r\n const decl = thenProp.valueDeclaration ?? thenProp.declarations?.[0];\r\n if (!decl) return false;\r\n\r\n const thenType = checker.getTypeOfSymbolAtLocation(thenProp, decl);\r\n return thenType.getCallSignatures().length > 0;\r\n}\r\n\r\nexport function isAwaited(node: ts.CallExpression) {\r\n return ts.isAwaitExpression(node.parent);\r\n}\r\n\r\nexport function isReturned(node: ts.CallExpression) {\r\n return ts.isReturnStatement(node.parent);\r\n}\r\n\r\nexport function isHandledWithThen(node: ts.CallExpression) {\r\n if (!ts.isPropertyAccessExpression(node.parent)) return false;\r\n const name = node.parent.name.text;\r\n return name === \"then\" || name === \"catch\" || name === \"finally\";\r\n}\r\n\r\nexport function isAsyncForEach(node: ts.CallExpression): boolean {\r\n if (!ts.isPropertyAccessExpression(node.expression)) return false;\r\n if (node.expression.name.text !== \"forEach\") return false;\r\n\r\n const cb = node.arguments[0];\r\n if (!cb) return false;\r\n\r\n if (ts.isArrowFunction(cb) || ts.isFunctionExpression(cb)) {\r\n return cb.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;\r\n }\r\n\r\n return false;\r\n}\r\n\r\nexport function isAsyncEventListener(node: ts.CallExpression): boolean {\r\n if (!ts.isPropertyAccessExpression(node.expression)) return false;\r\n if (node.expression.name.text !== \"addEventListener\") return false;\r\n\r\n const cb = node.arguments[1];\r\n if (!cb) return false;\r\n\r\n if (ts.isArrowFunction(cb) || ts.isFunctionExpression(cb)) {\r\n return cb.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;\r\n }\r\n\r\n return false;\r\n}\r\n\r\nexport function isPromiseAll(node: ts.CallExpression): boolean {\r\n return (\r\n ts.isPropertyAccessExpression(node.expression) &&\r\n ts.isIdentifier(node.expression.expression) &&\r\n node.expression.expression.text === \"Promise\" &&\r\n node.expression.name.text === \"all\"\r\n );\r\n}\r\n","import { CallGraph } from \"./buildCallGraph\";\r\nimport { PurityResult } from \"../../libs/types\";\r\n\r\nexport function propagateImpurity(\r\n graph: CallGraph,\r\n purity: PurityResult[]\r\n): PurityResult[] {\r\n\r\n // Map functionId -> PurityResult\r\n const resultMap = new Map<string, PurityResult>();\r\n for (const r of purity) {\r\n resultMap.set(r.id, r);\r\n }\r\n\r\n // Build reverse graph: calleeId -> Set<callerId>\r\n const reverse = new Map<string, Set<string>>();\r\n\r\n for (const [callerId, calleeIds] of graph.entries()) {\r\n for (const calleeId of calleeIds) {\r\n if (!reverse.has(calleeId)) {\r\n reverse.set(calleeId, new Set());\r\n }\r\n reverse.get(calleeId)!.add(callerId);\r\n }\r\n }\r\n\r\n // BFS propagation\r\n const queue: string[] = [];\r\n\r\n // Seed with initially impure functions\r\n for (const r of purity) {\r\n if (!r.isPure) {\r\n queue.push(r.id);\r\n }\r\n }\r\n\r\n while (queue.length > 0) {\r\n const impureId = queue.shift()!;\r\n const callers = reverse.get(impureId);\r\n if (!callers) continue;\r\n\r\n for (const callerId of callers) {\r\n const caller = resultMap.get(callerId);\r\n if (!caller) continue;\r\n\r\n if (caller.isPure) {\r\n caller.isPure = false;\r\n caller.reasons.push(`Calls impure function`);\r\n queue.push(callerId);\r\n }\r\n }\r\n }\r\n\r\n return Array.from(resultMap.values());\r\n}\r\n","#!/usr/bin/env node\r\nimport { createTsProgram } from \"./analyzer/createProgram\";\r\nimport { collectFunctions } from \"./analyzer/collectFunctions\";\r\nimport { buildCallGraph } from \"./analyzer/buildCallGraph\";\r\nimport { analyzePurity } from \"./analyzer/purity\";\r\nimport { propagateImpurity } from \"./analyzer/propagateImpurity\";\r\n\r\nconst command = process.argv[2];\r\n\r\nif (command !== \"analyze\") {\r\n console.log(\"Usage: callograph analyze\");\r\n process.exit(1);\r\n}\r\n\r\nconst program = createTsProgram(\"tsconfig.json\");\r\nconst functions = collectFunctions(program);\r\nconst graph = buildCallGraph(program, functions);\r\n\r\nlet purity = analyzePurity(program, functions);\r\npurity = propagateImpurity(graph, purity);\r\n\r\n/* ------------------------------\r\n CALL GRAPH\r\n--------------------------------*/\r\nconsole.log(\"\\nCall Graph:\\n\");\r\n\r\nlet edgeCount = 0;\r\n\r\nfor (const [callerId, callees] of graph.entries()) {\r\n const caller = functions.find(f => f.id === callerId);\r\n const callerName = caller ? caller.name : callerId;\r\n\r\n for (const calleeId of callees) {\r\n const callee = functions.find(f => f.id === calleeId);\r\n const calleeName = callee ? callee.name : calleeId;\r\n\r\n console.log(`${callerName} → ${calleeName}`);\r\n edgeCount++;\r\n }\r\n}\r\n\r\nif (edgeCount === 0) {\r\n console.log(\"(no project-level call edges found)\");\r\n}\r\n\r\nconsole.log(`\\nTotal call edges: ${edgeCount}`);\r\n\r\n/* ------------------------------\r\n PURITY REPORT\r\n--------------------------------*/\r\nconsole.log(\"\\nPurity Report:\\n\");\r\n\r\nconst impure = purity.filter(r => !r.isPure);\r\n\r\nif (impure.length === 0) {\r\n console.log(\"All analyzed functions are pure.\");\r\n} else {\r\n for (const r of impure) {\r\n console.log(`${r.file}:${r.line}:${r.character}`);\r\n console.log(`Function: ${r.name}`);\r\n console.log(`Status: X Impure`);\r\n console.log(\"Reasons:\");\r\n for (const reason of r.reasons) {\r\n console.log(` - ${reason}`);\r\n }\r\n console.log(\"\");\r\n }\r\n}\r\n"],"mappings":";;;;AAAA,OAAO,QAAQ;AAER,SAAS,gBAAgB,cAAsB;AACpD,QAAM,aAAa,GAAG,eAAe,cAAc,GAAG,IAAI,QAAQ;AAElE,MAAI,WAAW,OAAO;AACpB,UAAM,IAAI;AAAA,MACR,GAAG,6BAA6B,WAAW,MAAM,aAAa,IAAI;AAAA,IACpE;AAAA,EACF;AAEA,QAAM,eAAe,GAAG;AAAA,IACtB,WAAW;AAAA,IACX,GAAG;AAAA,IACH,QAAQ,IAAI;AAAA,EACd;AAEA,SAAO,GAAG,cAAc;AAAA,IACtB,WAAW,aAAa;AAAA,IACxB,SAAS,aAAa;AAAA,EACxB,CAAC;AACH;;;ACrBA,OAAOA,SAAQ;AAGf,SAAS,SAAS,MAAkC,IAA2B;AAC7E,QAAM,UAAU;AAGhB,MAAI,QAAQ,QAAQA,IAAG,aAAa,QAAQ,IAAI,EAAG,QAAO,QAAQ,KAAK;AAGvE,MACE,QAAQ,SACPA,IAAG,gBAAgB,QAAQ,IAAI,KAAKA,IAAG,iBAAiB,QAAQ,IAAI,IACrE;AACA,WAAO,OAAO,QAAQ,KAAK,IAAI;AAAA,EACjC;AAGA,QAAM,IAAI,KAAK;AACf,MAAI,KAAKA,IAAG,sBAAsB,CAAC,KAAKA,IAAG,aAAa,EAAE,IAAI,EAAG,QAAO,EAAE,KAAK;AAG/E,MAAI,KAAKA,IAAG,mBAAmB,CAAC,KAAKA,IAAG,2BAA2B,EAAE,IAAI,GAAG;AAC1E,WAAO,EAAE,KAAK,KAAK;AAAA,EACrB;AAEA,QAAM,EAAE,MAAM,UAAU,IAAI,GAAG,8BAA8B,KAAK,SAAS,EAAE,CAAC;AAC9E,SAAO,cAAc,OAAO,CAAC,IAAI,YAAY,CAAC;AAChD;AAEO,SAAS,iBAAiBC,UAAqC;AACpE,QAAM,MAAsB,CAAC;AAE7B,aAAW,MAAMA,SAAQ,eAAe,GAAG;AACzC,QAAI,GAAG,kBAAmB;AAC1B,QAAI,GAAG,SAAS,SAAS,cAAc,EAAG;AAE1C,IAAAD,IAAG,aAAa,IAAI,SAAS,MAAM,GAAG;AACpC,YAAM,sBAAsBA,IAAG,sBAAsB,CAAC,KAAK,CAAC,CAAC,EAAE;AAC/D,YAAM,WAAWA,IAAG,oBAAoB,CAAC;AAEzC,YAAM,IAAI,EAAE;AACZ,YAAM,iBACHA,IAAG,gBAAgB,CAAC,KAAKA,IAAG,qBAAqB,CAAC,MACnD,CAAC,CAAC,KACFA,IAAG,sBAAsB,CAAC,KAC1BA,IAAG,aAAa,EAAE,IAAI;AAExB,UAAI,uBAAuB,YAAY,eAAe;AACpD,cAAM,OAAO,SAAS,GAAG,EAAE;AAC3B,cAAM,KAAK,GAAG,GAAG,QAAQ,IAAI,EAAE,GAAG,IAAI,IAAI;AAC1C,YAAI,KAAK,EAAE,IAAI,MAAM,MAAM,GAAG,UAAU,MAAM,EAAE,CAAC;AAAA,MACnD;AAEA,MAAAA,IAAG,aAAa,GAAG,KAAK;AAAA,IAC1B,CAAC;AAAA,EACH;AAEA,SAAO;AACT;;;AC3DA,OAAOE,SAAQ;AAMf,SAAS,cAAc,UAAkB;AACvC,SAAO,CAAC,SAAS,SAAS,cAAc,KAAK,CAAC,SAAS,SAAS,OAAO;AACzE;AAEO,SAAS,eACdC,UACAC,YACW;AAEX,QAAM,UAAUD,SAAQ,eAAe;AACvC,QAAME,SAAmB,oBAAI,IAAI;AAEjC,QAAM,WAAW,oBAAI,IAA4B;AAEjD,aAAW,MAAMD,YAAW;AAC1B,IAAAC,OAAM,IAAI,GAAG,IAAI,oBAAI,IAAI,CAAC;AAC1B,aAAS,IAAI,GAAG,MAAM,GAAG,EAAE;AAAA,EAC7B;AAEA,aAAW,MAAMD,YAAW;AAG1B,QAASE,SAAT,SAAe,MAAe;AAC5B,UAAIJ,IAAG,iBAAiB,IAAI,GAAG;AAC7B,cAAM,MAAM,QAAQ,qBAAqB,IAAI;AAC7C,cAAM,OAAO,KAAK,eAAe;AAEjC,YACE,QACA,cAAc,KAAK,cAAc,EAAE,QAAQ,GAC3C;AACA,gBAAM,WAAW,SAAS,IAAI,IAAsB;AAEpD,cAAI,UAAU;AACZ,YAAAG,OAAM,IAAI,QAAQ,EAAG,IAAI,QAAQ;AAAA,UACnC;AAAA,QACF;AAAA,MACF;AAEA,MAAAH,IAAG,aAAa,MAAMI,MAAK;AAAA,IAC7B;AAlBS,gBAAAA;AAFT,UAAM,WAAW,GAAG;AAsBpB,IAAAA,OAAM,GAAG,IAAI;AAAA,EACf;AAEA,SAAOD;AACT;;;ACpDA,OAAOE,SAAQ;;;ACAR,IAAM,iBAAiB,oBAAI,IAAI;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,yBAAyB,oBAAI,IAAI;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;;;ACnBD,OAAOC,SAAQ;AAER,SAAS,SAAS,MAAe,WAAoB;AAC1D,SAAO,KAAK,OAAO,UAAU,OAAO,KAAK,OAAO,UAAU;AAC5D;AAEO,SAAS,eAAe,MAA2C;AACxE,MAAIA,IAAG,aAAa,IAAI,EAAG,QAAO;AAClC,MAAIA,IAAG,2BAA2B,IAAI,EAAG,QAAO,eAAe,KAAK,UAAU;AAC9E,MAAIA,IAAG,0BAA0B,IAAI,EAAG,QAAO,eAAe,KAAK,UAAU;AAC7E,MAAIA,IAAG,0BAA0B,IAAI,EAAG,QAAO,eAAe,KAAK,UAAU;AAC7E,SAAO;AACT;AAEO,SAAS,gBAAgB,MAAqB;AACnD,SACE,SAASA,IAAG,WAAW,eACvB,SAASA,IAAG,WAAW,mBACvB,SAASA,IAAG,WAAW,oBACvB,SAASA,IAAG,WAAW,uBACvB,SAASA,IAAG,WAAW,oBACvB,SAASA,IAAG,WAAW,sBACvB,SAASA,IAAG,WAAW,wBACvB,SAASA,IAAG,WAAW,kBACvB,SAASA,IAAG,WAAW,oBACvB,SAASA,IAAG,WAAW,+BACvB,SAASA,IAAG,WAAW,qCACvB,SAASA,IAAG,WAAW;AAE3B;;;AC7BA,OAAOC,SAAQ;AAER,SAAS,cAAc,MAAe,SAAkC;AAC7E,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,WAAW,KAAK,YAAY,MAAM;AACxC,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,OAAO,SAAS,oBAAoB,SAAS,eAAe,CAAC;AACnE,MAAI,CAAC,KAAM,QAAO;AAElB,QAAM,WAAW,QAAQ,0BAA0B,UAAU,IAAI;AACjE,SAAO,SAAS,kBAAkB,EAAE,SAAS;AAC/C;AAEO,SAAS,UAAU,MAAyB;AACjD,SAAOA,IAAG,kBAAkB,KAAK,MAAM;AACzC;AAEO,SAAS,WAAW,MAAyB;AAClD,SAAOA,IAAG,kBAAkB,KAAK,MAAM;AACzC;AAEO,SAAS,kBAAkB,MAAyB;AACzD,MAAI,CAACA,IAAG,2BAA2B,KAAK,MAAM,EAAG,QAAO;AACxD,QAAM,OAAO,KAAK,OAAO,KAAK;AAC9B,SAAO,SAAS,UAAU,SAAS,WAAW,SAAS;AACzD;AAEO,SAAS,eAAe,MAAkC;AAC/D,MAAI,CAACA,IAAG,2BAA2B,KAAK,UAAU,EAAG,QAAO;AAC5D,MAAI,KAAK,WAAW,KAAK,SAAS,UAAW,QAAO;AAEpD,QAAM,KAAK,KAAK,UAAU,CAAC;AAC3B,MAAI,CAAC,GAAI,QAAO;AAEhB,MAAIA,IAAG,gBAAgB,EAAE,KAAKA,IAAG,qBAAqB,EAAE,GAAG;AACzD,WAAO,GAAG,WAAW,KAAK,OAAK,EAAE,SAASA,IAAG,WAAW,YAAY,KAAK;AAAA,EAC3E;AAEA,SAAO;AACT;AAEO,SAAS,qBAAqB,MAAkC;AACrE,MAAI,CAACA,IAAG,2BAA2B,KAAK,UAAU,EAAG,QAAO;AAC5D,MAAI,KAAK,WAAW,KAAK,SAAS,mBAAoB,QAAO;AAE7D,QAAM,KAAK,KAAK,UAAU,CAAC;AAC3B,MAAI,CAAC,GAAI,QAAO;AAEhB,MAAIA,IAAG,gBAAgB,EAAE,KAAKA,IAAG,qBAAqB,EAAE,GAAG;AACzD,WAAO,GAAG,WAAW,KAAK,OAAK,EAAE,SAASA,IAAG,WAAW,YAAY,KAAK;AAAA,EAC3E;AAEA,SAAO;AACT;AAEO,SAAS,aAAa,MAAkC;AAC7D,SACEA,IAAG,2BAA2B,KAAK,UAAU,KAC7CA,IAAG,aAAa,KAAK,WAAW,UAAU,KAC1C,KAAK,WAAW,WAAW,SAAS,aACpC,KAAK,WAAW,KAAK,SAAS;AAElC;;;AHlDO,SAAS,cACdC,UACAC,YACgB;AAChB,QAAM,UAAUD,SAAQ,eAAe;AACvC,QAAM,UAA0B,CAAC;AAEjC,aAAW,MAAMC,YAAW;AAuB1B,QAASC,SAAT,SAAe,MAAqB;AAIlC,UAAIC,IAAG,mBAAmB,IAAI,KAAK,gBAAgB,KAAK,cAAc,IAAI,GAAG;AAC3E,cAAM,OAAO,KAAK;AAClB,cAAM,OAAO,eAAe,IAAI;AAEhC,YAAI,QAAQ,WAAW,IAAI,KAAK,IAAI,GAAG;AACrC,eAAK,uBAAuB,KAAK,IAAI,IAAI;AAAA,QAC3C;AAEA,YAAIA,IAAG,aAAa,IAAI,GAAG;AACzB,gBAAM,MAAM,QAAQ,oBAAoB,IAAI;AAC5C,gBAAM,QAAQ,KAAK,gBAAgB,KAAK,CAAC;AACzC,gBAAM,iBAAiB,MAAM,KAAK,OAAK,SAAS,GAAG,GAAG,IAAI,CAAC;AAC3D,cAAI,OAAO,MAAM,SAAS,KAAK,CAAC,gBAAgB;AAC9C,iBAAK,2BAA2B,KAAK,IAAI,IAAI;AAAA,UAC/C;AAAA,QACF;AAEA,YACEA,IAAG,2BAA2B,IAAI,KAClC,KAAK,WAAW,SAASA,IAAG,WAAW,aACvC;AACA,eAAK,kBAAkB,KAAK,KAAK,IAAI,IAAI;AAAA,QAC3C;AAAA,MACF;AAKA,UAAIA,IAAG,iBAAiB,IAAI,KAAKA,IAAG,2BAA2B,KAAK,UAAU,GAAG;AAC/E,cAAM,SAAS,KAAK,WAAW,KAAK;AACpC,cAAM,OAAO,eAAe,KAAK,WAAW,UAAU;AAEtD,YAAI,QAAQ,WAAW,IAAI,KAAK,IAAI,KAAK,uBAAuB,IAAI,MAAM,GAAG;AAC3E,eAAK,uBAAuB,KAAK,IAAI,YAAY,MAAM,MAAM;AAAA,QAC/D;AAAA,MACF;AAKA,UAAIA,IAAG,aAAa,IAAI,GAAG;AACzB,cAAM,MAAM,QAAQ,oBAAoB,IAAI;AAC5C,YAAI,OAAO,eAAe,IAAI,IAAI,QAAQ,CAAC,GAAG;AAC5C,eAAK,qBAAqB,IAAI,QAAQ,CAAC,IAAI;AAAA,QAC7C;AAAA,MACF;AAEA,UAAIA,IAAG,2BAA2B,IAAI,GAAG;AACvC,cAAM,OAAO,eAAe,KAAK,UAAU;AAE3C,YAAI,MAAM,SAAS,aAAa,KAAK,KAAK,SAAS,OAAO;AACxD,eAAK,4CAA4C;AAAA,QACnD;AAEA,YAAI,MAAM,SAAS,WAAW;AAC5B,eAAK,mBAAmB;AAAA,QAC1B;AAAA,MACF;AAKA,UAAIA,IAAG,iBAAiB,IAAI,GAAG;AAC7B,cAAM,OAAO,QAAQ,kBAAkB,IAAI;AAC3C,cAAM,OAAO,KAAK;AAGlB,YACEA,IAAG,2BAA2B,IAAI,KAClCA,IAAG,aAAa,KAAK,UAAU,KAC/B,KAAK,WAAW,SAAS,UACzB,KAAK,KAAK,SAAS,OACnB;AACA,eAAK,sCAAsC;AAAA,QAC7C;AAEA,YACEA,IAAG,2BAA2B,IAAI,KAClCA,IAAG,aAAa,KAAK,UAAU,KAC/B,KAAK,WAAW,SAAS,UACzB,KAAK,KAAK,SAAS,UACnB;AACA,eAAK,yCAAyC;AAAA,QAChD;AAGA,YAAI,cAAc,MAAM,OAAO,GAAG;AAChC,cAAI,CAAC,UAAU,IAAI,KAAK,CAAC,WAAW,IAAI,KAAK,CAAC,kBAAkB,IAAI,GAAG;AACrE,iBAAK,mCAAmC;AAAA,UAC1C;AAAA,QACF;AAEA,YAAI,aAAa,IAAI,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,WAAW,IAAI,GAAG;AAC/D,eAAK,yBAAyB;AAAA,QAChC;AAEA,YAAI,eAAe,IAAI,GAAG;AACxB,eAAK,6CAA6C;AAAA,QACpD;AAEA,YAAI,qBAAqB,IAAI,GAAG;AAC9B,eAAK,6CAA6C;AAAA,QACpD;AAAA,MACF;AAKA,UAAIA,IAAG,gBAAgB,IAAI,GAAG;AAC5B,YAAIA,IAAG,aAAa,KAAK,UAAU,KAAK,KAAK,WAAW,SAAS,QAAQ;AACvE,eAAK,uCAAuC;AAAA,QAC9C;AAAA,MACF;AAKA,UAAIA,IAAG,kBAAkB,IAAI,GAAG;AAC9B,wBAAgB;AAAA,MAClB;AAEA,UAAIA,IAAG,eAAe,IAAI,KAAK,KAAK,aAAa;AAC/C,sBAAc;AAAA,MAChB;AAEA,UAAIA,IAAG,cAAc,IAAI,KAAK,KAAK,MAAM,WAAW,WAAW,GAAG;AAChE,aAAK,qCAAqC;AAAA,MAC5C;AAEA,MAAAA,IAAG,aAAa,MAAMD,MAAK;AAAA,IAC7B;AAtIS,gBAAAA;AAtBT,UAAM,KAAK,GAAG,KAAK,cAAc;AACjC,UAAM,EAAE,MAAM,UAAU,IAAI,GAAG;AAAA,MAC7B,GAAG,KAAK,SAAS,EAAE;AAAA,IACrB;AAEA,UAAM,UAAoB,CAAC;AAC3B,UAAM,aAAa,oBAAI,IAAY;AAEnC,eAAW,KAAK,GAAG,KAAK,YAAY;AAClC,UAAIC,IAAG,aAAa,EAAE,IAAI,EAAG,YAAW,IAAI,EAAE,KAAK,IAAI;AAAA,IACzD;AAEA,UAAM,OAAO,CAAC,WAAmB;AAC/B,UAAI,CAAC,QAAQ,SAAS,MAAM,EAAG,SAAQ,KAAK,MAAM;AAAA,IACpD;AAEA,QAAI,gBAAgB;AACpB,QAAI,cAAc;AAElB,UAAM,kBACJ,GAAG,KAAK,WAAW,KAAK,OAAK,EAAE,SAASA,IAAG,WAAW,YAAY,KAAK;AA0IzE,IAAAD,OAAM,GAAG,IAAI;AAEb,QAAI,mBAAmB,CAAC,eAAe;AACrC,WAAK,8BAA8B;AAAA,IACrC;AAEA,QAAI,mBAAmB,iBAAiB,CAAC,aAAa;AACpD,WAAK,sDAAsD;AAAA,IAC7D;AAEA,YAAQ,KAAK;AAAA,MACX,IAAI,GAAG;AAAA,MACP,MAAM,GAAG;AAAA,MACT,MAAM,GAAG;AAAA,MACT,MAAM,OAAO;AAAA,MACb,WAAW,YAAY;AAAA,MACvB,QAAQ,QAAQ,WAAW;AAAA,MAC3B;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;;;AIvMO,SAAS,kBACdE,QACAC,SACgB;AAGhB,QAAM,YAAY,oBAAI,IAA0B;AAChD,aAAW,KAAKA,SAAQ;AACtB,cAAU,IAAI,EAAE,IAAI,CAAC;AAAA,EACvB;AAGA,QAAM,UAAU,oBAAI,IAAyB;AAE7C,aAAW,CAAC,UAAU,SAAS,KAAKD,OAAM,QAAQ,GAAG;AACnD,eAAW,YAAY,WAAW;AAChC,UAAI,CAAC,QAAQ,IAAI,QAAQ,GAAG;AAC1B,gBAAQ,IAAI,UAAU,oBAAI,IAAI,CAAC;AAAA,MACjC;AACA,cAAQ,IAAI,QAAQ,EAAG,IAAI,QAAQ;AAAA,IACrC;AAAA,EACF;AAGA,QAAM,QAAkB,CAAC;AAGzB,aAAW,KAAKC,SAAQ;AACtB,QAAI,CAAC,EAAE,QAAQ;AACb,YAAM,KAAK,EAAE,EAAE;AAAA,IACjB;AAAA,EACF;AAEA,SAAO,MAAM,SAAS,GAAG;AACvB,UAAM,WAAW,MAAM,MAAM;AAC7B,UAAM,UAAU,QAAQ,IAAI,QAAQ;AACpC,QAAI,CAAC,QAAS;AAEd,eAAW,YAAY,SAAS;AAC9B,YAAM,SAAS,UAAU,IAAI,QAAQ;AACrC,UAAI,CAAC,OAAQ;AAEb,UAAI,OAAO,QAAQ;AACjB,eAAO,SAAS;AAChB,eAAO,QAAQ,KAAK,uBAAuB;AAC3C,cAAM,KAAK,QAAQ;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,UAAU,OAAO,CAAC;AACtC;;;AC/CA,IAAM,UAAU,QAAQ,KAAK,CAAC;AAE9B,IAAI,YAAY,WAAW;AACzB,UAAQ,IAAI,2BAA2B;AACvC,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,UAAU,gBAAgB,eAAe;AAC/C,IAAM,YAAY,iBAAiB,OAAO;AAC1C,IAAM,QAAQ,eAAe,SAAS,SAAS;AAE/C,IAAI,SAAS,cAAc,SAAS,SAAS;AAC7C,SAAS,kBAAkB,OAAO,MAAM;AAKxC,QAAQ,IAAI,iBAAiB;AAE7B,IAAI,YAAY;AAEhB,WAAW,CAAC,UAAU,OAAO,KAAK,MAAM,QAAQ,GAAG;AACjD,QAAM,SAAS,UAAU,KAAK,OAAK,EAAE,OAAO,QAAQ;AACpD,QAAM,aAAa,SAAS,OAAO,OAAO;AAE1C,aAAW,YAAY,SAAS;AAC9B,UAAM,SAAS,UAAU,KAAK,OAAK,EAAE,OAAO,QAAQ;AACpD,UAAM,aAAa,SAAS,OAAO,OAAO;AAE1C,YAAQ,IAAI,GAAG,UAAU,WAAM,UAAU,EAAE;AAC3C;AAAA,EACF;AACF;AAEA,IAAI,cAAc,GAAG;AACnB,UAAQ,IAAI,qCAAqC;AACnD;AAEA,QAAQ,IAAI;AAAA,oBAAuB,SAAS,EAAE;AAK9C,QAAQ,IAAI,oBAAoB;AAEhC,IAAM,SAAS,OAAO,OAAO,OAAK,CAAC,EAAE,MAAM;AAE3C,IAAI,OAAO,WAAW,GAAG;AACvB,UAAQ,IAAI,kCAAkC;AAChD,OAAO;AACL,aAAW,KAAK,QAAQ;AACtB,YAAQ,IAAI,GAAG,EAAE,IAAI,IAAI,EAAE,IAAI,IAAI,EAAE,SAAS,EAAE;AAChD,YAAQ,IAAI,aAAa,EAAE,IAAI,EAAE;AACjC,YAAQ,IAAI,kBAAkB;AAC9B,YAAQ,IAAI,UAAU;AACtB,eAAW,UAAU,EAAE,SAAS;AAC9B,cAAQ,IAAI,OAAO,MAAM,EAAE;AAAA,IAC7B;AACA,YAAQ,IAAI,EAAE;AAAA,EAChB;AACF;","names":["ts","program","ts","program","functions","graph","visit","ts","ts","ts","program","functions","visit","ts","graph","purity"]}
package/dist/index.cjs CHANGED
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/env node
1
2
  "use strict";
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export const version = \"0.0.2\";"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,IAAM,UAAU;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export const version = \"0.0.2\";"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,IAAM,UAAU;","names":[]}
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ #!/usr/bin/env node
2
+
1
3
  // src/index.ts
2
4
  var version = "0.0.2";
3
5
  export {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export const version = \"0.0.2\";"],"mappings":";AAAO,IAAM,UAAU;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export const version = \"0.0.2\";"],"mappings":";;;AAAO,IAAM,UAAU;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "callograph",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "TypeScript call graph and side-effect analyzer.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,7 +23,7 @@
23
23
  "typescript": "^5.9.3"
24
24
  },
25
25
  "bin": {
26
- "callograph": "./dist/cli.cjs"
26
+ "callograph": "dist/cli.cjs"
27
27
  },
28
28
  "repository": {
29
29
  "type": "git",