circle-ir 3.31.0 → 3.33.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.
@@ -9605,6 +9605,448 @@ function findChildByTypeGo(node, type) {
9605
9605
  return null;
9606
9606
  }
9607
9607
 
9608
+ // src/core/extractors/runtime-registrations.ts
9609
+ var HTTP_VERB_METHODS = /* @__PURE__ */ new Set([
9610
+ "get",
9611
+ "post",
9612
+ "put",
9613
+ "patch",
9614
+ "delete",
9615
+ "head",
9616
+ "options",
9617
+ "all"
9618
+ ]);
9619
+ var MIDDLEWARE_METHODS = /* @__PURE__ */ new Set(["use"]);
9620
+ var EVENT_LISTENER_METHODS = /* @__PURE__ */ new Set(["on", "once", "ws"]);
9621
+ var EXPRESS_RECEIVER_NAMES = /* @__PURE__ */ new Set([
9622
+ "app",
9623
+ "router",
9624
+ "server",
9625
+ "apiRouter",
9626
+ // common framework instances
9627
+ "fastify",
9628
+ "koa",
9629
+ "express"
9630
+ ]);
9631
+ var FRAMEWORK_MODULE_PATTERNS = [
9632
+ /^express$/,
9633
+ /^@?fastify(\/.*)?$/,
9634
+ /^koa$/,
9635
+ /^restify$/,
9636
+ /^hapi$/,
9637
+ /^@nestjs\/common$/,
9638
+ /^@nestjs\/core$/
9639
+ ];
9640
+ function extractRuntimeRegistrations(tree, cache, language, imports) {
9641
+ if (language === "javascript" || language === "typescript") {
9642
+ return extractJSRuntimeRegistrations(tree, cache, imports);
9643
+ }
9644
+ if (language === "python") {
9645
+ return extractPythonRuntimeRegistrations(tree, cache, imports);
9646
+ }
9647
+ return [];
9648
+ }
9649
+ function buildHandlerIndex(tree, cache, imports) {
9650
+ const decls = /* @__PURE__ */ new Map();
9651
+ const recordDeclaration = (name2, node) => {
9652
+ if (!decls.has(name2)) {
9653
+ decls.set(name2, {
9654
+ line: node.startPosition.row + 1,
9655
+ column: node.startPosition.column
9656
+ });
9657
+ }
9658
+ };
9659
+ for (const fn of getNodesFromCache(tree.rootNode, "function_declaration", cache)) {
9660
+ const nameNode = fn.childForFieldName("name");
9661
+ if (nameNode) recordDeclaration(getNodeText(nameNode), fn);
9662
+ }
9663
+ const collectVarDeclarators = (parentType) => {
9664
+ for (const decl of getNodesFromCache(tree.rootNode, parentType, cache)) {
9665
+ for (let i2 = 0; i2 < decl.childCount; i2++) {
9666
+ const child = decl.child(i2);
9667
+ if (!child || child.type !== "variable_declarator") continue;
9668
+ const nameNode = child.childForFieldName("name");
9669
+ const valueNode = child.childForFieldName("value");
9670
+ if (!nameNode || !valueNode) continue;
9671
+ if (valueNode.type === "arrow_function" || valueNode.type === "function_expression" || valueNode.type === "function") {
9672
+ recordDeclaration(getNodeText(nameNode), child);
9673
+ }
9674
+ }
9675
+ }
9676
+ };
9677
+ collectVarDeclarators("lexical_declaration");
9678
+ collectVarDeclarators("variable_declaration");
9679
+ let hasFrameworkImport = false;
9680
+ if (imports) {
9681
+ for (const imp of imports) {
9682
+ const mod = imp.from_package ?? "";
9683
+ if (mod && FRAMEWORK_MODULE_PATTERNS.some((re) => re.test(mod))) {
9684
+ hasFrameworkImport = true;
9685
+ break;
9686
+ }
9687
+ }
9688
+ }
9689
+ return { declarations: decls, hasFrameworkImport };
9690
+ }
9691
+ function extractJSRuntimeRegistrations(tree, cache, imports) {
9692
+ const out2 = [];
9693
+ const index = buildHandlerIndex(tree, cache, imports);
9694
+ const callExpressions = getNodesFromCache(tree.rootNode, "call_expression", cache);
9695
+ for (const call of callExpressions) {
9696
+ const fnNode = call.childForFieldName("function");
9697
+ if (!fnNode || fnNode.type !== "member_expression") continue;
9698
+ const objectNode = fnNode.childForFieldName("object");
9699
+ const propertyNode = fnNode.childForFieldName("property");
9700
+ if (!objectNode || !propertyNode) continue;
9701
+ const method = getNodeText(propertyNode);
9702
+ const receiver = getNodeText(objectNode);
9703
+ const kind = classifyMethod(method);
9704
+ if (!kind) continue;
9705
+ if (!isExpressShapedReceiver(receiver) && !index.hasFrameworkImport) {
9706
+ continue;
9707
+ }
9708
+ const argsNode = call.childForFieldName("arguments");
9709
+ if (!argsNode) continue;
9710
+ const argNodes = getRealArgs(argsNode);
9711
+ if (argNodes.length === 0) continue;
9712
+ let path;
9713
+ let handlerStart = 0;
9714
+ const first = argNodes[0];
9715
+ if (first.type === "string") {
9716
+ path = stripQuotes(getNodeText(first));
9717
+ handlerStart = 1;
9718
+ } else if (first.type === "template_string" && !hasTemplateSubstitution(first)) {
9719
+ path = stripBackticks(getNodeText(first));
9720
+ handlerStart = 1;
9721
+ }
9722
+ for (let i2 = handlerStart; i2 < argNodes.length; i2++) {
9723
+ const handlerNode = argNodes[i2];
9724
+ const handler = resolveHandler(handlerNode, index);
9725
+ if (!handler) continue;
9726
+ out2.push({
9727
+ kind,
9728
+ framework: inferFramework(receiver, index.hasFrameworkImport),
9729
+ registrar: {
9730
+ method,
9731
+ receiver,
9732
+ line: call.startPosition.row + 1,
9733
+ column: call.startPosition.column
9734
+ },
9735
+ ...path !== void 0 ? { path } : {},
9736
+ handler
9737
+ });
9738
+ }
9739
+ }
9740
+ return out2;
9741
+ }
9742
+ function classifyMethod(method) {
9743
+ if (HTTP_VERB_METHODS.has(method)) return "http_route";
9744
+ if (MIDDLEWARE_METHODS.has(method)) return "middleware";
9745
+ if (EVENT_LISTENER_METHODS.has(method)) return "event_listener";
9746
+ return null;
9747
+ }
9748
+ function isExpressShapedReceiver(receiver) {
9749
+ if (EXPRESS_RECEIVER_NAMES.has(receiver)) return true;
9750
+ if (/(?:Router|App|Server)$/.test(receiver)) return true;
9751
+ return false;
9752
+ }
9753
+ function inferFramework(receiver, hasFrameworkImport) {
9754
+ if (receiver === "fastify") return "fastify";
9755
+ if (receiver === "koa") return "koa";
9756
+ if (receiver === "express") return "express";
9757
+ return hasFrameworkImport ? "express" : "unknown";
9758
+ }
9759
+ function getRealArgs(argsNode) {
9760
+ const out2 = [];
9761
+ for (let i2 = 0; i2 < argsNode.childCount; i2++) {
9762
+ const child = argsNode.child(i2);
9763
+ if (!child) continue;
9764
+ if (child.type === "(" || child.type === ")" || child.type === ",") continue;
9765
+ out2.push(child);
9766
+ }
9767
+ return out2;
9768
+ }
9769
+ function stripQuotes(s) {
9770
+ if (s.length >= 2 && (s[0] === '"' || s[0] === "'") && s[s.length - 1] === s[0]) {
9771
+ return s.slice(1, -1);
9772
+ }
9773
+ return s;
9774
+ }
9775
+ function stripBackticks(s) {
9776
+ if (s.length >= 2 && s[0] === "`" && s[s.length - 1] === "`") {
9777
+ return s.slice(1, -1);
9778
+ }
9779
+ return s;
9780
+ }
9781
+ function hasTemplateSubstitution(node) {
9782
+ for (let i2 = 0; i2 < node.childCount; i2++) {
9783
+ const child = node.child(i2);
9784
+ if (child && child.type === "template_substitution") return true;
9785
+ }
9786
+ return false;
9787
+ }
9788
+ function resolveHandler(node, index) {
9789
+ if (node.type === "arrow_function" || node.type === "function_expression" || node.type === "function") {
9790
+ return {
9791
+ name: null,
9792
+ line: node.startPosition.row + 1,
9793
+ column: node.startPosition.column
9794
+ };
9795
+ }
9796
+ if (node.type === "identifier") {
9797
+ const name2 = getNodeText(node);
9798
+ const decl = index.declarations.get(name2);
9799
+ if (decl) {
9800
+ return { name: name2, line: decl.line, column: decl.column };
9801
+ }
9802
+ return {
9803
+ name: name2,
9804
+ line: node.startPosition.row + 1,
9805
+ column: node.startPosition.column
9806
+ };
9807
+ }
9808
+ if (node.type === "member_expression") {
9809
+ return {
9810
+ name: getNodeText(node),
9811
+ line: node.startPosition.row + 1,
9812
+ column: node.startPosition.column
9813
+ };
9814
+ }
9815
+ return null;
9816
+ }
9817
+ var PY_HTTP_ROUTE_METHODS = /* @__PURE__ */ new Set([
9818
+ // Flask/Blueprint: app.route, blueprint.route, api.route
9819
+ "route",
9820
+ // FastAPI / Starlette / DRF method-specific
9821
+ "get",
9822
+ "post",
9823
+ "put",
9824
+ "patch",
9825
+ "delete",
9826
+ "head",
9827
+ "options"
9828
+ // Flask aliases (Flask 2.x): app.get/post/...
9829
+ ]);
9830
+ var PY_MIDDLEWARE_METHODS = /* @__PURE__ */ new Set([
9831
+ "before_request",
9832
+ "after_request",
9833
+ "teardown_request",
9834
+ "before_first_request",
9835
+ "teardown_appcontext",
9836
+ // Starlette / FastAPI
9837
+ "middleware"
9838
+ ]);
9839
+ var PY_EVENT_METHODS = /* @__PURE__ */ new Set([
9840
+ "errorhandler",
9841
+ "on_event",
9842
+ "exception_handler"
9843
+ // Celery beat etc — not strictly events but lifecycle
9844
+ ]);
9845
+ var PY_STDLIB_DECORATORS = /* @__PURE__ */ new Set([
9846
+ "property",
9847
+ "staticmethod",
9848
+ "classmethod",
9849
+ "abstractmethod",
9850
+ "cached_property",
9851
+ "dataclass",
9852
+ "cache",
9853
+ "lru_cache",
9854
+ "singledispatch",
9855
+ "singledispatchmethod",
9856
+ "contextmanager",
9857
+ "asynccontextmanager",
9858
+ "final",
9859
+ "override",
9860
+ "wraps"
9861
+ ]);
9862
+ function summarisePythonImports(imports) {
9863
+ const s = {
9864
+ hasFlask: false,
9865
+ hasFastApi: false,
9866
+ hasCelery: false,
9867
+ hasNumba: false,
9868
+ hasClick: false,
9869
+ hasPytest: false
9870
+ };
9871
+ if (!imports) return s;
9872
+ for (const imp of imports) {
9873
+ const mod = imp.from_package ?? "";
9874
+ if (!mod) continue;
9875
+ if (/^flask(\b|\.)/.test(mod)) s.hasFlask = true;
9876
+ if (/^fastapi(\b|\.)/.test(mod) || /^starlette(\b|\.)/.test(mod)) s.hasFastApi = true;
9877
+ if (/^celery(\b|\.)/.test(mod)) s.hasCelery = true;
9878
+ if (/^numba(\b|\.)/.test(mod)) s.hasNumba = true;
9879
+ if (/^click(\b|\.)/.test(mod)) s.hasClick = true;
9880
+ if (/^pytest(\b|\.)/.test(mod)) s.hasPytest = true;
9881
+ }
9882
+ return s;
9883
+ }
9884
+ function extractPythonRuntimeRegistrations(tree, cache, imports) {
9885
+ const out2 = [];
9886
+ const importSummary = summarisePythonImports(imports);
9887
+ const decoratedDefs = getNodesFromCache(tree.rootNode, "decorated_definition", cache);
9888
+ for (const dd of decoratedDefs) {
9889
+ let fnNode = null;
9890
+ const decorators = [];
9891
+ for (let i2 = 0; i2 < dd.childCount; i2++) {
9892
+ const child = dd.child(i2);
9893
+ if (!child) continue;
9894
+ if (child.type === "decorator") {
9895
+ decorators.push(child);
9896
+ } else if (child.type === "function_definition" || child.type === "async_function_definition") {
9897
+ fnNode = child;
9898
+ }
9899
+ }
9900
+ if (!fnNode || decorators.length === 0) continue;
9901
+ const handler = pythonHandlerFromFunctionDef(fnNode);
9902
+ if (!handler) continue;
9903
+ for (const dec of decorators) {
9904
+ const parsed = parsePythonDecorator(dec);
9905
+ if (!parsed) continue;
9906
+ const { receiver, method, path, line, column } = parsed;
9907
+ const { kind, framework } = classifyPythonDecorator(
9908
+ receiver,
9909
+ method,
9910
+ importSummary
9911
+ );
9912
+ out2.push({
9913
+ kind,
9914
+ framework,
9915
+ registrar: { method, receiver, line, column },
9916
+ ...path !== void 0 ? { path } : {},
9917
+ handler
9918
+ });
9919
+ }
9920
+ }
9921
+ return out2;
9922
+ }
9923
+ function pythonHandlerFromFunctionDef(fn) {
9924
+ const nameNode = fn.childForFieldName("name");
9925
+ if (!nameNode) return null;
9926
+ return {
9927
+ name: getNodeText(nameNode),
9928
+ line: fn.startPosition.row + 1,
9929
+ column: fn.startPosition.column
9930
+ };
9931
+ }
9932
+ function parsePythonDecorator(dec) {
9933
+ let target = null;
9934
+ for (let i2 = 0; i2 < dec.childCount; i2++) {
9935
+ const child = dec.child(i2);
9936
+ if (!child || child.type === "@") continue;
9937
+ target = child;
9938
+ break;
9939
+ }
9940
+ if (!target) return null;
9941
+ const line = dec.startPosition.row + 1;
9942
+ const column = dec.startPosition.column;
9943
+ if (target.type === "identifier") {
9944
+ return { receiver: "", method: getNodeText(target), line, column };
9945
+ }
9946
+ if (target.type === "attribute") {
9947
+ const { receiver, method } = splitDottedAttribute(target);
9948
+ return { receiver, method, line, column };
9949
+ }
9950
+ if (target.type === "call") {
9951
+ const fnNode = target.childForFieldName("function");
9952
+ if (!fnNode) return null;
9953
+ let receiver = "";
9954
+ let method = "";
9955
+ if (fnNode.type === "identifier") {
9956
+ method = getNodeText(fnNode);
9957
+ } else if (fnNode.type === "attribute") {
9958
+ const split = splitDottedAttribute(fnNode);
9959
+ receiver = split.receiver;
9960
+ method = split.method;
9961
+ } else {
9962
+ method = getNodeText(fnNode);
9963
+ }
9964
+ const path = extractFirstStringArg(target);
9965
+ return { receiver, method, path, line, column };
9966
+ }
9967
+ return null;
9968
+ }
9969
+ function splitDottedAttribute(attr) {
9970
+ const objectNode = attr.childForFieldName("object");
9971
+ const attrNode = attr.childForFieldName("attribute");
9972
+ const method = attrNode ? getNodeText(attrNode) : "";
9973
+ const receiver = objectNode ? getNodeText(objectNode) : "";
9974
+ return { receiver, method };
9975
+ }
9976
+ function extractFirstStringArg(call) {
9977
+ const argsNode = call.childForFieldName("arguments");
9978
+ if (!argsNode) return void 0;
9979
+ for (let i2 = 0; i2 < argsNode.childCount; i2++) {
9980
+ const child = argsNode.child(i2);
9981
+ if (!child) continue;
9982
+ if (child.type === "(" || child.type === ")" || child.type === ",") continue;
9983
+ if (child.type === "string") {
9984
+ return stripPythonStringQuotes(getNodeText(child));
9985
+ }
9986
+ return void 0;
9987
+ }
9988
+ return void 0;
9989
+ }
9990
+ function stripPythonStringQuotes(s) {
9991
+ const m = s.match(/^[bBrRuUfF]{0,2}(['"])(.*)\1$/s);
9992
+ if (m) return m[2];
9993
+ if (s.length >= 2 && (s[0] === '"' || s[0] === "'") && s[s.length - 1] === s[0]) {
9994
+ return s.slice(1, -1);
9995
+ }
9996
+ return s;
9997
+ }
9998
+ function classifyPythonDecorator(receiver, method, imp) {
9999
+ if (!receiver && PY_STDLIB_DECORATORS.has(method)) {
10000
+ return { kind: "decorator", framework: "stdlib" };
10001
+ }
10002
+ if (receiver) {
10003
+ const head = receiver.split(".")[0];
10004
+ if (head === "pytest") {
10005
+ return { kind: "decorator", framework: "pytest" };
10006
+ }
10007
+ if (head === "click") {
10008
+ return { kind: "decorator", framework: "click" };
10009
+ }
10010
+ if (head === "numba" || head === "nb") {
10011
+ return { kind: "decorator", framework: "numba" };
10012
+ }
10013
+ if (head === "celery") {
10014
+ return { kind: "decorator", framework: "celery" };
10015
+ }
10016
+ }
10017
+ if (receiver && PY_HTTP_ROUTE_METHODS.has(method)) {
10018
+ const isRoutey = isPyRouterReceiver(receiver);
10019
+ if (isRoutey) {
10020
+ let framework = "unknown";
10021
+ if (imp.hasFlask) framework = "flask";
10022
+ else if (imp.hasFastApi) framework = "fastapi";
10023
+ else if (method === "route") framework = "flask";
10024
+ else framework = "fastapi";
10025
+ return { kind: "http_route", framework };
10026
+ }
10027
+ }
10028
+ if (receiver && PY_MIDDLEWARE_METHODS.has(method)) {
10029
+ return { kind: "middleware", framework: imp.hasFlask ? "flask" : imp.hasFastApi ? "fastapi" : "unknown" };
10030
+ }
10031
+ if (receiver && PY_EVENT_METHODS.has(method)) {
10032
+ return { kind: "event_listener", framework: imp.hasFlask ? "flask" : imp.hasFastApi ? "fastapi" : "unknown" };
10033
+ }
10034
+ if (method === "task" && imp.hasCelery) {
10035
+ return { kind: "decorator", framework: "celery" };
10036
+ }
10037
+ if (!receiver && (method === "login_required" || method === "require_http_methods" || method === "api_view")) {
10038
+ return { kind: "decorator", framework: "django" };
10039
+ }
10040
+ return { kind: "decorator", framework: "unknown" };
10041
+ }
10042
+ function isPyRouterReceiver(receiver) {
10043
+ const head = receiver.split(".")[0];
10044
+ if (!head) return false;
10045
+ if (["app", "router", "blueprint", "bp", "api", "application"].includes(head)) return true;
10046
+ if (/_(router|bp|blueprint|app|api)$/.test(head)) return true;
10047
+ return false;
10048
+ }
10049
+
9608
10050
  // src/analysis/config-loader.ts
9609
10051
  var DEFAULT_SOURCES = [
9610
10052
  // HTTP Sources (Servlet API)
@@ -19778,7 +20220,7 @@ function extractEventHandlers(elementNode, eventHandlers) {
19778
20220
  if (!EVENT_HANDLER_ATTRS.has(attrName)) continue;
19779
20221
  const valueNode = findChildByType2(child, "quoted_attribute_value") ?? findChildByType2(child, "attribute_value");
19780
20222
  if (!valueNode) continue;
19781
- const code = stripQuotes(valueNode.text);
20223
+ const code = stripQuotes2(valueNode.text);
19782
20224
  if (code) {
19783
20225
  eventHandlers.push({
19784
20226
  code,
@@ -19809,7 +20251,7 @@ function getAttributeValue(tag, name2) {
19809
20251
  const nameNode = findChildByType2(child, "attribute_name");
19810
20252
  if (nameNode?.text.toLowerCase() === name2) {
19811
20253
  const valueNode = findChildByType2(child, "quoted_attribute_value") ?? findChildByType2(child, "attribute_value");
19812
- return valueNode ? stripQuotes(valueNode.text) : "";
20254
+ return valueNode ? stripQuotes2(valueNode.text) : "";
19813
20255
  }
19814
20256
  }
19815
20257
  return void 0;
@@ -19821,7 +20263,7 @@ function findChildByType2(node, type) {
19821
20263
  }
19822
20264
  return null;
19823
20265
  }
19824
- function stripQuotes(text) {
20266
+ function stripQuotes2(text) {
19825
20267
  if (text.startsWith('"') && text.endsWith('"') || text.startsWith("'") && text.endsWith("'")) {
19826
20268
  return text.slice(1, -1);
19827
20269
  }
@@ -25024,7 +25466,7 @@ var SecurityHeadersPass = class {
25024
25466
  };
25025
25467
  function literalOf(arg) {
25026
25468
  if (arg.literal !== null && arg.literal !== void 0 && arg.literal !== "") {
25027
- return stripQuotes2(arg.literal);
25469
+ return stripQuotes3(arg.literal);
25028
25470
  }
25029
25471
  const expr = arg.expression.trim();
25030
25472
  if (expr.startsWith('"') && expr.endsWith('"') || expr.startsWith("'") && expr.endsWith("'") || expr.startsWith("`") && expr.endsWith("`")) {
@@ -25033,7 +25475,7 @@ function literalOf(arg) {
25033
25475
  }
25034
25476
  return null;
25035
25477
  }
25036
- function stripQuotes2(s) {
25478
+ function stripQuotes3(s) {
25037
25479
  if (s.length < 2) return s;
25038
25480
  const first = s[0];
25039
25481
  const last = s[s.length - 1];
@@ -26120,7 +26562,9 @@ function getNodeTypesForLanguage(language) {
26120
26562
  "import_from_statement",
26121
26563
  "assignment",
26122
26564
  "attribute",
26123
- "subscript"
26565
+ "subscript",
26566
+ "decorated_definition",
26567
+ "decorator"
26124
26568
  ]);
26125
26569
  case "javascript":
26126
26570
  case "typescript":
@@ -26212,6 +26656,7 @@ async function analyze(code, filePath, language, options = {}) {
26212
26656
  const exports = extractExports(types);
26213
26657
  const cfg = buildCFG(tree, language);
26214
26658
  const dfg = buildDFG(tree, nodeCache, language);
26659
+ const runtimeRegistrations = extractRuntimeRegistrations(tree, nodeCache, language, imports);
26215
26660
  const graph = new CodeGraph({
26216
26661
  meta,
26217
26662
  types,
@@ -26306,7 +26751,8 @@ async function analyze(code, filePath, language, options = {}) {
26306
26751
  unresolved,
26307
26752
  enriched,
26308
26753
  findings: findings.length > 0 ? findings : void 0,
26309
- metrics: { file: filePath, metrics: metricValues }
26754
+ metrics: { file: filePath, metrics: metricValues },
26755
+ runtime_registrations: runtimeRegistrations.length > 0 ? runtimeRegistrations : void 0
26310
26756
  };
26311
26757
  } finally {
26312
26758
  disposeTree(tree);
@@ -8,4 +8,5 @@ export { extractImports } from './imports.js';
8
8
  export { extractExports } from './exports.js';
9
9
  export { buildCFG } from './cfg.js';
10
10
  export { buildDFG } from './dfg.js';
11
+ export { extractRuntimeRegistrations } from './runtime-registrations.js';
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/extractors/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/extractors/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC"}
@@ -8,4 +8,5 @@ export { extractImports } from './imports.js';
8
8
  export { extractExports } from './exports.js';
9
9
  export { buildCFG } from './cfg.js';
10
10
  export { buildDFG } from './dfg.js';
11
+ export { extractRuntimeRegistrations } from './runtime-registrations.js';
11
12
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/extractors/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/extractors/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,2BAA2B,EAAE,MAAM,4BAA4B,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Runtime-registration extractor (issue #15 — Phases 1 + 2).
3
+ *
4
+ * Recognises framework registration patterns where a handler is wired into a
5
+ * dispatch table at module-load time. Static call extraction sees the
6
+ * registration call/decorator but not the edge from registrar → handler.
7
+ *
8
+ * Downstream consumers (e.g. dead-code reachability) read
9
+ * `ir.runtime_registrations` and add each resolved handler as a virtual entry
10
+ * root, eliminating "unreachable" false positives for framework handlers.
11
+ *
12
+ * Phase 1 — JS/TS Express-family (shipped 3.32.0):
13
+ * - HTTP routes: `app.METHOD(path?, ...handlers)` for METHOD ∈ HTTP_VERBS
14
+ * - Middleware: `app.use(...handlers)`
15
+ * - Event listeners: `emitter.on('event', handler)` on express-shaped receivers
16
+ *
17
+ * Phase 2 — Python decorators (3.33.0):
18
+ * - Every `@decorator` on a function emits a registration with handler =
19
+ * decorated function. Known frameworks are tagged (flask, fastapi,
20
+ * django, click, pytest, celery, numba); built-in (property,
21
+ * staticmethod, etc.) is tagged `stdlib`. Routing-style decorators
22
+ * (`@app.route`, `@app.get`, `@router.post`) are classified as
23
+ * `kind: 'http_route'` so downstream consumers can treat JS routes and
24
+ * Python routes uniformly.
25
+ *
26
+ * Out of scope (Phase 3):
27
+ * - Rust trait dispatch (`impl Trait for Type`, `Box<dyn Trait>`,
28
+ * `inventory::submit!`, `linkme::distributed_slice`).
29
+ * - Subapp mounting (`app.use('/api', subApp)`) handler resolution.
30
+ */
31
+ import type { Tree } from 'web-tree-sitter';
32
+ import type { RuntimeRegistration, ImportInfo } from '../../types/index.js';
33
+ import { type NodeCache } from '../parser.js';
34
+ import type { SupportedLanguage } from '../parser.js';
35
+ /**
36
+ * Extract runtime-registration patterns from a parsed file.
37
+ *
38
+ * Phase 1 covers JavaScript/TypeScript. Phase 2 adds Python decorators.
39
+ * Returns `[]` for any other language.
40
+ */
41
+ export declare function extractRuntimeRegistrations(tree: Tree, cache: NodeCache | undefined, language: SupportedLanguage | string, imports?: ImportInfo[]): RuntimeRegistration[];
42
+ //# sourceMappingURL=runtime-registrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-registrations.d.ts","sourceRoot":"","sources":["../../../src/core/extractors/runtime-registrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAQ,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAkC,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AA2BtD;;;;;GAKG;AACH,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,IAAI,EACV,KAAK,EAAE,SAAS,GAAG,SAAS,EAC5B,QAAQ,EAAE,iBAAiB,GAAG,MAAM,EACpC,OAAO,CAAC,EAAE,UAAU,EAAE,GACrB,mBAAmB,EAAE,CAQvB"}