circle-ir 3.32.0 → 3.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +2 -0
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +503 -5
- package/dist/core/extractors/runtime-registrations.d.ts +34 -14
- package/dist/core/extractors/runtime-registrations.d.ts.map +1 -1
- package/dist/core/extractors/runtime-registrations.js +596 -17
- package/dist/core/extractors/runtime-registrations.js.map +1 -1
- package/dist/types/index.d.ts +23 -5
- package/dist/types/index.d.ts.map +1 -1
- package/docs/SPEC.md +2 -0
- package/package.json +1 -1
|
@@ -9638,10 +9638,16 @@ var FRAMEWORK_MODULE_PATTERNS = [
|
|
|
9638
9638
|
/^@nestjs\/core$/
|
|
9639
9639
|
];
|
|
9640
9640
|
function extractRuntimeRegistrations(tree, cache, language, imports) {
|
|
9641
|
-
if (language
|
|
9642
|
-
return
|
|
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
|
+
if (language === "rust") {
|
|
9648
|
+
return extractRustRuntimeRegistrations(tree, cache);
|
|
9643
9649
|
}
|
|
9644
|
-
return
|
|
9650
|
+
return [];
|
|
9645
9651
|
}
|
|
9646
9652
|
function buildHandlerIndex(tree, cache, imports) {
|
|
9647
9653
|
const decls = /* @__PURE__ */ new Map();
|
|
@@ -9811,6 +9817,494 @@ function resolveHandler(node, index) {
|
|
|
9811
9817
|
}
|
|
9812
9818
|
return null;
|
|
9813
9819
|
}
|
|
9820
|
+
var PY_HTTP_ROUTE_METHODS = /* @__PURE__ */ new Set([
|
|
9821
|
+
// Flask/Blueprint: app.route, blueprint.route, api.route
|
|
9822
|
+
"route",
|
|
9823
|
+
// FastAPI / Starlette / DRF method-specific
|
|
9824
|
+
"get",
|
|
9825
|
+
"post",
|
|
9826
|
+
"put",
|
|
9827
|
+
"patch",
|
|
9828
|
+
"delete",
|
|
9829
|
+
"head",
|
|
9830
|
+
"options"
|
|
9831
|
+
// Flask aliases (Flask 2.x): app.get/post/...
|
|
9832
|
+
]);
|
|
9833
|
+
var PY_MIDDLEWARE_METHODS = /* @__PURE__ */ new Set([
|
|
9834
|
+
"before_request",
|
|
9835
|
+
"after_request",
|
|
9836
|
+
"teardown_request",
|
|
9837
|
+
"before_first_request",
|
|
9838
|
+
"teardown_appcontext",
|
|
9839
|
+
// Starlette / FastAPI
|
|
9840
|
+
"middleware"
|
|
9841
|
+
]);
|
|
9842
|
+
var PY_EVENT_METHODS = /* @__PURE__ */ new Set([
|
|
9843
|
+
"errorhandler",
|
|
9844
|
+
"on_event",
|
|
9845
|
+
"exception_handler"
|
|
9846
|
+
// Celery beat etc — not strictly events but lifecycle
|
|
9847
|
+
]);
|
|
9848
|
+
var PY_STDLIB_DECORATORS = /* @__PURE__ */ new Set([
|
|
9849
|
+
"property",
|
|
9850
|
+
"staticmethod",
|
|
9851
|
+
"classmethod",
|
|
9852
|
+
"abstractmethod",
|
|
9853
|
+
"cached_property",
|
|
9854
|
+
"dataclass",
|
|
9855
|
+
"cache",
|
|
9856
|
+
"lru_cache",
|
|
9857
|
+
"singledispatch",
|
|
9858
|
+
"singledispatchmethod",
|
|
9859
|
+
"contextmanager",
|
|
9860
|
+
"asynccontextmanager",
|
|
9861
|
+
"final",
|
|
9862
|
+
"override",
|
|
9863
|
+
"wraps"
|
|
9864
|
+
]);
|
|
9865
|
+
function summarisePythonImports(imports) {
|
|
9866
|
+
const s = {
|
|
9867
|
+
hasFlask: false,
|
|
9868
|
+
hasFastApi: false,
|
|
9869
|
+
hasCelery: false,
|
|
9870
|
+
hasNumba: false,
|
|
9871
|
+
hasClick: false,
|
|
9872
|
+
hasPytest: false
|
|
9873
|
+
};
|
|
9874
|
+
if (!imports) return s;
|
|
9875
|
+
for (const imp of imports) {
|
|
9876
|
+
const mod = imp.from_package ?? "";
|
|
9877
|
+
if (!mod) continue;
|
|
9878
|
+
if (/^flask(\b|\.)/.test(mod)) s.hasFlask = true;
|
|
9879
|
+
if (/^fastapi(\b|\.)/.test(mod) || /^starlette(\b|\.)/.test(mod)) s.hasFastApi = true;
|
|
9880
|
+
if (/^celery(\b|\.)/.test(mod)) s.hasCelery = true;
|
|
9881
|
+
if (/^numba(\b|\.)/.test(mod)) s.hasNumba = true;
|
|
9882
|
+
if (/^click(\b|\.)/.test(mod)) s.hasClick = true;
|
|
9883
|
+
if (/^pytest(\b|\.)/.test(mod)) s.hasPytest = true;
|
|
9884
|
+
}
|
|
9885
|
+
return s;
|
|
9886
|
+
}
|
|
9887
|
+
function extractPythonRuntimeRegistrations(tree, cache, imports) {
|
|
9888
|
+
const out2 = [];
|
|
9889
|
+
const importSummary = summarisePythonImports(imports);
|
|
9890
|
+
const decoratedDefs = getNodesFromCache(tree.rootNode, "decorated_definition", cache);
|
|
9891
|
+
for (const dd of decoratedDefs) {
|
|
9892
|
+
let fnNode = null;
|
|
9893
|
+
const decorators = [];
|
|
9894
|
+
for (let i2 = 0; i2 < dd.childCount; i2++) {
|
|
9895
|
+
const child = dd.child(i2);
|
|
9896
|
+
if (!child) continue;
|
|
9897
|
+
if (child.type === "decorator") {
|
|
9898
|
+
decorators.push(child);
|
|
9899
|
+
} else if (child.type === "function_definition" || child.type === "async_function_definition") {
|
|
9900
|
+
fnNode = child;
|
|
9901
|
+
}
|
|
9902
|
+
}
|
|
9903
|
+
if (!fnNode || decorators.length === 0) continue;
|
|
9904
|
+
const handler = pythonHandlerFromFunctionDef(fnNode);
|
|
9905
|
+
if (!handler) continue;
|
|
9906
|
+
for (const dec of decorators) {
|
|
9907
|
+
const parsed = parsePythonDecorator(dec);
|
|
9908
|
+
if (!parsed) continue;
|
|
9909
|
+
const { receiver, method, path, line, column } = parsed;
|
|
9910
|
+
const { kind, framework } = classifyPythonDecorator(
|
|
9911
|
+
receiver,
|
|
9912
|
+
method,
|
|
9913
|
+
importSummary
|
|
9914
|
+
);
|
|
9915
|
+
out2.push({
|
|
9916
|
+
kind,
|
|
9917
|
+
framework,
|
|
9918
|
+
registrar: { method, receiver, line, column },
|
|
9919
|
+
...path !== void 0 ? { path } : {},
|
|
9920
|
+
handler
|
|
9921
|
+
});
|
|
9922
|
+
}
|
|
9923
|
+
}
|
|
9924
|
+
return out2;
|
|
9925
|
+
}
|
|
9926
|
+
function pythonHandlerFromFunctionDef(fn) {
|
|
9927
|
+
const nameNode = fn.childForFieldName("name");
|
|
9928
|
+
if (!nameNode) return null;
|
|
9929
|
+
return {
|
|
9930
|
+
name: getNodeText(nameNode),
|
|
9931
|
+
line: fn.startPosition.row + 1,
|
|
9932
|
+
column: fn.startPosition.column
|
|
9933
|
+
};
|
|
9934
|
+
}
|
|
9935
|
+
function parsePythonDecorator(dec) {
|
|
9936
|
+
let target = null;
|
|
9937
|
+
for (let i2 = 0; i2 < dec.childCount; i2++) {
|
|
9938
|
+
const child = dec.child(i2);
|
|
9939
|
+
if (!child || child.type === "@") continue;
|
|
9940
|
+
target = child;
|
|
9941
|
+
break;
|
|
9942
|
+
}
|
|
9943
|
+
if (!target) return null;
|
|
9944
|
+
const line = dec.startPosition.row + 1;
|
|
9945
|
+
const column = dec.startPosition.column;
|
|
9946
|
+
if (target.type === "identifier") {
|
|
9947
|
+
return { receiver: "", method: getNodeText(target), line, column };
|
|
9948
|
+
}
|
|
9949
|
+
if (target.type === "attribute") {
|
|
9950
|
+
const { receiver, method } = splitDottedAttribute(target);
|
|
9951
|
+
return { receiver, method, line, column };
|
|
9952
|
+
}
|
|
9953
|
+
if (target.type === "call") {
|
|
9954
|
+
const fnNode = target.childForFieldName("function");
|
|
9955
|
+
if (!fnNode) return null;
|
|
9956
|
+
let receiver = "";
|
|
9957
|
+
let method = "";
|
|
9958
|
+
if (fnNode.type === "identifier") {
|
|
9959
|
+
method = getNodeText(fnNode);
|
|
9960
|
+
} else if (fnNode.type === "attribute") {
|
|
9961
|
+
const split = splitDottedAttribute(fnNode);
|
|
9962
|
+
receiver = split.receiver;
|
|
9963
|
+
method = split.method;
|
|
9964
|
+
} else {
|
|
9965
|
+
method = getNodeText(fnNode);
|
|
9966
|
+
}
|
|
9967
|
+
const path = extractFirstStringArg(target);
|
|
9968
|
+
return { receiver, method, path, line, column };
|
|
9969
|
+
}
|
|
9970
|
+
return null;
|
|
9971
|
+
}
|
|
9972
|
+
function splitDottedAttribute(attr) {
|
|
9973
|
+
const objectNode = attr.childForFieldName("object");
|
|
9974
|
+
const attrNode = attr.childForFieldName("attribute");
|
|
9975
|
+
const method = attrNode ? getNodeText(attrNode) : "";
|
|
9976
|
+
const receiver = objectNode ? getNodeText(objectNode) : "";
|
|
9977
|
+
return { receiver, method };
|
|
9978
|
+
}
|
|
9979
|
+
function extractFirstStringArg(call) {
|
|
9980
|
+
const argsNode = call.childForFieldName("arguments");
|
|
9981
|
+
if (!argsNode) return void 0;
|
|
9982
|
+
for (let i2 = 0; i2 < argsNode.childCount; i2++) {
|
|
9983
|
+
const child = argsNode.child(i2);
|
|
9984
|
+
if (!child) continue;
|
|
9985
|
+
if (child.type === "(" || child.type === ")" || child.type === ",") continue;
|
|
9986
|
+
if (child.type === "string") {
|
|
9987
|
+
return stripPythonStringQuotes(getNodeText(child));
|
|
9988
|
+
}
|
|
9989
|
+
return void 0;
|
|
9990
|
+
}
|
|
9991
|
+
return void 0;
|
|
9992
|
+
}
|
|
9993
|
+
function stripPythonStringQuotes(s) {
|
|
9994
|
+
const m = s.match(/^[bBrRuUfF]{0,2}(['"])(.*)\1$/s);
|
|
9995
|
+
if (m) return m[2];
|
|
9996
|
+
if (s.length >= 2 && (s[0] === '"' || s[0] === "'") && s[s.length - 1] === s[0]) {
|
|
9997
|
+
return s.slice(1, -1);
|
|
9998
|
+
}
|
|
9999
|
+
return s;
|
|
10000
|
+
}
|
|
10001
|
+
function classifyPythonDecorator(receiver, method, imp) {
|
|
10002
|
+
if (!receiver && PY_STDLIB_DECORATORS.has(method)) {
|
|
10003
|
+
return { kind: "decorator", framework: "stdlib" };
|
|
10004
|
+
}
|
|
10005
|
+
if (receiver) {
|
|
10006
|
+
const head = receiver.split(".")[0];
|
|
10007
|
+
if (head === "pytest") {
|
|
10008
|
+
return { kind: "decorator", framework: "pytest" };
|
|
10009
|
+
}
|
|
10010
|
+
if (head === "click") {
|
|
10011
|
+
return { kind: "decorator", framework: "click" };
|
|
10012
|
+
}
|
|
10013
|
+
if (head === "numba" || head === "nb") {
|
|
10014
|
+
return { kind: "decorator", framework: "numba" };
|
|
10015
|
+
}
|
|
10016
|
+
if (head === "celery") {
|
|
10017
|
+
return { kind: "decorator", framework: "celery" };
|
|
10018
|
+
}
|
|
10019
|
+
}
|
|
10020
|
+
if (receiver && PY_HTTP_ROUTE_METHODS.has(method)) {
|
|
10021
|
+
const isRoutey = isPyRouterReceiver(receiver);
|
|
10022
|
+
if (isRoutey) {
|
|
10023
|
+
let framework = "unknown";
|
|
10024
|
+
if (imp.hasFlask) framework = "flask";
|
|
10025
|
+
else if (imp.hasFastApi) framework = "fastapi";
|
|
10026
|
+
else if (method === "route") framework = "flask";
|
|
10027
|
+
else framework = "fastapi";
|
|
10028
|
+
return { kind: "http_route", framework };
|
|
10029
|
+
}
|
|
10030
|
+
}
|
|
10031
|
+
if (receiver && PY_MIDDLEWARE_METHODS.has(method)) {
|
|
10032
|
+
return { kind: "middleware", framework: imp.hasFlask ? "flask" : imp.hasFastApi ? "fastapi" : "unknown" };
|
|
10033
|
+
}
|
|
10034
|
+
if (receiver && PY_EVENT_METHODS.has(method)) {
|
|
10035
|
+
return { kind: "event_listener", framework: imp.hasFlask ? "flask" : imp.hasFastApi ? "fastapi" : "unknown" };
|
|
10036
|
+
}
|
|
10037
|
+
if (method === "task" && imp.hasCelery) {
|
|
10038
|
+
return { kind: "decorator", framework: "celery" };
|
|
10039
|
+
}
|
|
10040
|
+
if (!receiver && (method === "login_required" || method === "require_http_methods" || method === "api_view")) {
|
|
10041
|
+
return { kind: "decorator", framework: "django" };
|
|
10042
|
+
}
|
|
10043
|
+
return { kind: "decorator", framework: "unknown" };
|
|
10044
|
+
}
|
|
10045
|
+
function isPyRouterReceiver(receiver) {
|
|
10046
|
+
const head = receiver.split(".")[0];
|
|
10047
|
+
if (!head) return false;
|
|
10048
|
+
if (["app", "router", "blueprint", "bp", "api", "application"].includes(head)) return true;
|
|
10049
|
+
if (/_(router|bp|blueprint|app|api)$/.test(head)) return true;
|
|
10050
|
+
return false;
|
|
10051
|
+
}
|
|
10052
|
+
var RUST_STDLIB_TRAITS = /* @__PURE__ */ new Set([
|
|
10053
|
+
// Formatting
|
|
10054
|
+
"Display",
|
|
10055
|
+
"Debug",
|
|
10056
|
+
"Write",
|
|
10057
|
+
// Conversion
|
|
10058
|
+
"From",
|
|
10059
|
+
"Into",
|
|
10060
|
+
"TryFrom",
|
|
10061
|
+
"TryInto",
|
|
10062
|
+
"AsRef",
|
|
10063
|
+
"AsMut",
|
|
10064
|
+
"ToString",
|
|
10065
|
+
"FromStr",
|
|
10066
|
+
// Iteration
|
|
10067
|
+
"Iterator",
|
|
10068
|
+
"IntoIterator",
|
|
10069
|
+
"FromIterator",
|
|
10070
|
+
"DoubleEndedIterator",
|
|
10071
|
+
"ExactSizeIterator",
|
|
10072
|
+
"FusedIterator",
|
|
10073
|
+
// Comparison + hashing
|
|
10074
|
+
"PartialEq",
|
|
10075
|
+
"Eq",
|
|
10076
|
+
"PartialOrd",
|
|
10077
|
+
"Ord",
|
|
10078
|
+
"Hash",
|
|
10079
|
+
// Markers + defaults
|
|
10080
|
+
"Default",
|
|
10081
|
+
"Copy",
|
|
10082
|
+
"Clone",
|
|
10083
|
+
"Send",
|
|
10084
|
+
"Sync",
|
|
10085
|
+
"Unpin",
|
|
10086
|
+
"Sized",
|
|
10087
|
+
"Any",
|
|
10088
|
+
// Resource management
|
|
10089
|
+
"Drop",
|
|
10090
|
+
// Async
|
|
10091
|
+
"Future",
|
|
10092
|
+
"IntoFuture",
|
|
10093
|
+
// Operators
|
|
10094
|
+
"Add",
|
|
10095
|
+
"Sub",
|
|
10096
|
+
"Mul",
|
|
10097
|
+
"Div",
|
|
10098
|
+
"Rem",
|
|
10099
|
+
"Neg",
|
|
10100
|
+
"Not",
|
|
10101
|
+
"AddAssign",
|
|
10102
|
+
"SubAssign",
|
|
10103
|
+
"MulAssign",
|
|
10104
|
+
"DivAssign",
|
|
10105
|
+
"RemAssign",
|
|
10106
|
+
"BitAnd",
|
|
10107
|
+
"BitOr",
|
|
10108
|
+
"BitXor",
|
|
10109
|
+
"Shl",
|
|
10110
|
+
"Shr",
|
|
10111
|
+
"Deref",
|
|
10112
|
+
"DerefMut",
|
|
10113
|
+
"Index",
|
|
10114
|
+
"IndexMut",
|
|
10115
|
+
// Closures
|
|
10116
|
+
"Fn",
|
|
10117
|
+
"FnMut",
|
|
10118
|
+
"FnOnce",
|
|
10119
|
+
// Error + I/O
|
|
10120
|
+
"Error",
|
|
10121
|
+
"Read",
|
|
10122
|
+
"Write",
|
|
10123
|
+
"Seek",
|
|
10124
|
+
"BufRead",
|
|
10125
|
+
// Misc
|
|
10126
|
+
"Borrow",
|
|
10127
|
+
"BorrowMut",
|
|
10128
|
+
"ToOwned"
|
|
10129
|
+
]);
|
|
10130
|
+
var RUST_TRAIT_FRAMEWORK_PREFIXES = [
|
|
10131
|
+
{ prefix: /^actix(_web)?(::|$)/, framework: "actix" },
|
|
10132
|
+
{ prefix: /^axum(::|$)/, framework: "axum" },
|
|
10133
|
+
{ prefix: /^rocket(::|$)/, framework: "rocket" },
|
|
10134
|
+
{ prefix: /^tokio(::|$)/, framework: "tokio" },
|
|
10135
|
+
{ prefix: /^serde(_\w+)?(::|$)/, framework: "serde" },
|
|
10136
|
+
{ prefix: /^std(::|$)/, framework: "stdlib" },
|
|
10137
|
+
{ prefix: /^core(::|$)/, framework: "stdlib" },
|
|
10138
|
+
{ prefix: /^alloc(::|$)/, framework: "stdlib" }
|
|
10139
|
+
];
|
|
10140
|
+
function extractRustRuntimeRegistrations(tree, cache) {
|
|
10141
|
+
const regs = [];
|
|
10142
|
+
const implNodes = getNodesFromCache(tree.rootNode, "impl_item", cache);
|
|
10143
|
+
for (const impl of implNodes) {
|
|
10144
|
+
collectRustImplRegistrations(impl, regs);
|
|
10145
|
+
}
|
|
10146
|
+
const macroNodes = getNodesFromCache(tree.rootNode, "macro_invocation", cache);
|
|
10147
|
+
for (const macro of macroNodes) {
|
|
10148
|
+
const rec = parseInventorySubmit(macro);
|
|
10149
|
+
if (rec) regs.push(rec);
|
|
10150
|
+
}
|
|
10151
|
+
const attrNodes = getNodesFromCache(tree.rootNode, "attribute_item", cache);
|
|
10152
|
+
for (const attr of attrNodes) {
|
|
10153
|
+
const rec = parseDistributedSliceAttribute(attr);
|
|
10154
|
+
if (rec) regs.push(rec);
|
|
10155
|
+
}
|
|
10156
|
+
return regs;
|
|
10157
|
+
}
|
|
10158
|
+
function collectRustImplRegistrations(impl, regs) {
|
|
10159
|
+
const traitNode = impl.childForFieldName("trait");
|
|
10160
|
+
if (!traitNode) return;
|
|
10161
|
+
const typeNode = impl.childForFieldName("type");
|
|
10162
|
+
if (!typeNode) return;
|
|
10163
|
+
const traitText = getNodeText(traitNode).trim();
|
|
10164
|
+
const traitLastSegment = lastRustPathSegment(stripRustGenerics(traitText));
|
|
10165
|
+
const selfType = getNodeText(typeNode).trim();
|
|
10166
|
+
const framework = classifyRustTrait(traitText);
|
|
10167
|
+
const body2 = impl.childForFieldName("body");
|
|
10168
|
+
if (!body2) return;
|
|
10169
|
+
for (let i2 = 0; i2 < body2.childCount; i2++) {
|
|
10170
|
+
const child = body2.child(i2);
|
|
10171
|
+
if (!child || child.type !== "function_item") continue;
|
|
10172
|
+
const nameNode = child.childForFieldName("name");
|
|
10173
|
+
if (!nameNode) continue;
|
|
10174
|
+
const methodName = getNodeText(nameNode);
|
|
10175
|
+
regs.push({
|
|
10176
|
+
kind: "trait_impl",
|
|
10177
|
+
framework,
|
|
10178
|
+
registrar: {
|
|
10179
|
+
method: methodName,
|
|
10180
|
+
receiver: selfType,
|
|
10181
|
+
line: impl.startPosition.row + 1,
|
|
10182
|
+
column: impl.startPosition.column
|
|
10183
|
+
},
|
|
10184
|
+
path: traitLastSegment || traitText,
|
|
10185
|
+
handler: {
|
|
10186
|
+
name: methodName,
|
|
10187
|
+
line: child.startPosition.row + 1,
|
|
10188
|
+
column: child.startPosition.column
|
|
10189
|
+
}
|
|
10190
|
+
});
|
|
10191
|
+
}
|
|
10192
|
+
}
|
|
10193
|
+
function stripRustGenerics(text) {
|
|
10194
|
+
const idx = text.indexOf("<");
|
|
10195
|
+
return idx >= 0 ? text.slice(0, idx) : text;
|
|
10196
|
+
}
|
|
10197
|
+
function lastRustPathSegment(path) {
|
|
10198
|
+
const parts2 = path.split("::");
|
|
10199
|
+
return parts2[parts2.length - 1] || path;
|
|
10200
|
+
}
|
|
10201
|
+
function classifyRustTrait(traitText) {
|
|
10202
|
+
const stripped = stripRustGenerics(traitText).trim();
|
|
10203
|
+
const last = lastRustPathSegment(stripped);
|
|
10204
|
+
if (RUST_STDLIB_TRAITS.has(last)) return "stdlib";
|
|
10205
|
+
for (const { prefix, framework } of RUST_TRAIT_FRAMEWORK_PREFIXES) {
|
|
10206
|
+
if (prefix.test(stripped)) return framework;
|
|
10207
|
+
}
|
|
10208
|
+
return "unknown";
|
|
10209
|
+
}
|
|
10210
|
+
function parseInventorySubmit(macro) {
|
|
10211
|
+
const macroName = macro.childForFieldName("macro");
|
|
10212
|
+
if (!macroName) return null;
|
|
10213
|
+
const name2 = getNodeText(macroName).trim();
|
|
10214
|
+
if (name2 !== "inventory::submit" && name2 !== "submit") return null;
|
|
10215
|
+
if (name2 === "submit") return null;
|
|
10216
|
+
let tokenTree = null;
|
|
10217
|
+
for (let i2 = 0; i2 < macro.childCount; i2++) {
|
|
10218
|
+
const c = macro.child(i2);
|
|
10219
|
+
if (c && c.type === "token_tree") {
|
|
10220
|
+
tokenTree = c;
|
|
10221
|
+
break;
|
|
10222
|
+
}
|
|
10223
|
+
}
|
|
10224
|
+
if (!tokenTree) return null;
|
|
10225
|
+
const handlerName = firstIdentifierInTokenTree(tokenTree);
|
|
10226
|
+
return {
|
|
10227
|
+
kind: "trait_impl",
|
|
10228
|
+
framework: "inventory",
|
|
10229
|
+
registrar: {
|
|
10230
|
+
method: "submit",
|
|
10231
|
+
receiver: "inventory",
|
|
10232
|
+
line: macro.startPosition.row + 1,
|
|
10233
|
+
column: macro.startPosition.column
|
|
10234
|
+
},
|
|
10235
|
+
path: "inventory::submit",
|
|
10236
|
+
handler: {
|
|
10237
|
+
name: handlerName,
|
|
10238
|
+
line: tokenTree.startPosition.row + 1,
|
|
10239
|
+
column: tokenTree.startPosition.column
|
|
10240
|
+
}
|
|
10241
|
+
};
|
|
10242
|
+
}
|
|
10243
|
+
function firstIdentifierInTokenTree(tokenTree) {
|
|
10244
|
+
for (let i2 = 0; i2 < tokenTree.childCount; i2++) {
|
|
10245
|
+
const c = tokenTree.child(i2);
|
|
10246
|
+
if (!c) continue;
|
|
10247
|
+
if (c.type === "identifier" || c.type === "scoped_identifier" || c.type === "type_identifier") {
|
|
10248
|
+
return getNodeText(c).trim();
|
|
10249
|
+
}
|
|
10250
|
+
}
|
|
10251
|
+
return null;
|
|
10252
|
+
}
|
|
10253
|
+
function parseDistributedSliceAttribute(attrItem) {
|
|
10254
|
+
let attr = null;
|
|
10255
|
+
for (let i2 = 0; i2 < attrItem.childCount; i2++) {
|
|
10256
|
+
const c = attrItem.child(i2);
|
|
10257
|
+
if (c && c.type === "attribute") {
|
|
10258
|
+
attr = c;
|
|
10259
|
+
break;
|
|
10260
|
+
}
|
|
10261
|
+
}
|
|
10262
|
+
if (!attr) return null;
|
|
10263
|
+
const pathNode = attr.child(0);
|
|
10264
|
+
if (!pathNode) return null;
|
|
10265
|
+
const pathText = getNodeText(pathNode).trim();
|
|
10266
|
+
if (pathText !== "linkme::distributed_slice" && pathText !== "distributed_slice") return null;
|
|
10267
|
+
const parent = attrItem.parent;
|
|
10268
|
+
if (!parent) return null;
|
|
10269
|
+
let attrIndex = -1;
|
|
10270
|
+
for (let i2 = 0; i2 < parent.childCount; i2++) {
|
|
10271
|
+
const c = parent.child(i2);
|
|
10272
|
+
if (c && c.id === attrItem.id) {
|
|
10273
|
+
attrIndex = i2;
|
|
10274
|
+
break;
|
|
10275
|
+
}
|
|
10276
|
+
}
|
|
10277
|
+
if (attrIndex < 0) return null;
|
|
10278
|
+
let handlerNode = null;
|
|
10279
|
+
for (let j = attrIndex + 1; j < parent.childCount; j++) {
|
|
10280
|
+
const sib = parent.child(j);
|
|
10281
|
+
if (!sib) continue;
|
|
10282
|
+
if (sib.type === "attribute_item") continue;
|
|
10283
|
+
if (sib.type === "static_item" || sib.type === "function_item") {
|
|
10284
|
+
handlerNode = sib;
|
|
10285
|
+
}
|
|
10286
|
+
break;
|
|
10287
|
+
}
|
|
10288
|
+
if (!handlerNode) return null;
|
|
10289
|
+
const nameNode = handlerNode.childForFieldName("name");
|
|
10290
|
+
const handlerName = nameNode ? getNodeText(nameNode).trim() : null;
|
|
10291
|
+
return {
|
|
10292
|
+
kind: "trait_impl",
|
|
10293
|
+
framework: "linkme",
|
|
10294
|
+
registrar: {
|
|
10295
|
+
method: "distributed_slice",
|
|
10296
|
+
receiver: "linkme",
|
|
10297
|
+
line: attrItem.startPosition.row + 1,
|
|
10298
|
+
column: attrItem.startPosition.column
|
|
10299
|
+
},
|
|
10300
|
+
path: "linkme::distributed_slice",
|
|
10301
|
+
handler: {
|
|
10302
|
+
name: handlerName,
|
|
10303
|
+
line: handlerNode.startPosition.row + 1,
|
|
10304
|
+
column: handlerNode.startPosition.column
|
|
10305
|
+
}
|
|
10306
|
+
};
|
|
10307
|
+
}
|
|
9814
10308
|
|
|
9815
10309
|
// src/analysis/config-loader.ts
|
|
9816
10310
|
var DEFAULT_SOURCES = [
|
|
@@ -26316,7 +26810,9 @@ function getNodeTypesForLanguage(language) {
|
|
|
26316
26810
|
"use_declaration",
|
|
26317
26811
|
"let_declaration",
|
|
26318
26812
|
"field_expression",
|
|
26319
|
-
"scoped_identifier"
|
|
26813
|
+
"scoped_identifier",
|
|
26814
|
+
"attribute_item",
|
|
26815
|
+
"static_item"
|
|
26320
26816
|
]);
|
|
26321
26817
|
case "python":
|
|
26322
26818
|
return /* @__PURE__ */ new Set([
|
|
@@ -26327,7 +26823,9 @@ function getNodeTypesForLanguage(language) {
|
|
|
26327
26823
|
"import_from_statement",
|
|
26328
26824
|
"assignment",
|
|
26329
26825
|
"attribute",
|
|
26330
|
-
"subscript"
|
|
26826
|
+
"subscript",
|
|
26827
|
+
"decorated_definition",
|
|
26828
|
+
"decorator"
|
|
26331
26829
|
]);
|
|
26332
26830
|
case "javascript":
|
|
26333
26831
|
case "typescript":
|
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Runtime-registration extractor (issue #15 —
|
|
2
|
+
* Runtime-registration extractor (issue #15 — Phases 1, 2, 3).
|
|
3
3
|
*
|
|
4
|
-
* Recognises
|
|
5
|
-
*
|
|
6
|
-
* registration call
|
|
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
7
|
*
|
|
8
8
|
* Downstream consumers (e.g. dead-code reachability) read
|
|
9
9
|
* `ir.runtime_registrations` and add each resolved handler as a virtual entry
|
|
10
10
|
* root, eliminating "unreachable" false positives for framework handlers.
|
|
11
11
|
*
|
|
12
|
-
* Phase 1
|
|
13
|
-
* -
|
|
14
|
-
* where METHOD ∈ HTTP_VERBS and receiver is express-shaped
|
|
12
|
+
* Phase 1 — JS/TS Express-family (shipped 3.32.0):
|
|
13
|
+
* - HTTP routes: `app.METHOD(path?, ...handlers)` for METHOD ∈ HTTP_VERBS
|
|
15
14
|
* - Middleware: `app.use(...handlers)`
|
|
16
|
-
* - Event listeners: `emitter.on('event', handler)`
|
|
17
|
-
* express-shaped, otherwise skipped to avoid false-positive registrations)
|
|
15
|
+
* - Event listeners: `emitter.on('event', handler)` on express-shaped receivers
|
|
18
16
|
*
|
|
19
|
-
*
|
|
20
|
-
* -
|
|
21
|
-
*
|
|
22
|
-
*
|
|
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
|
+
* Phase 3 — Rust trait dispatch (3.34.0):
|
|
27
|
+
* - `impl Trait for Type { fn method(...) }` emits one `trait_impl`
|
|
28
|
+
* registration per method, recording the Self type as `receiver`, the
|
|
29
|
+
* trait path as `path`, and the method as both `registrar.method` and
|
|
30
|
+
* `handler.name`. Stdlib traits (Display, Debug, Iterator, …) are tagged
|
|
31
|
+
* `framework: 'stdlib'`; known web/async/serde frameworks (actix, axum,
|
|
32
|
+
* rocket, tokio, serde) are tagged accordingly.
|
|
33
|
+
* - `inventory::submit! { … }` and `#[linkme::distributed_slice]` emit
|
|
34
|
+
* `trait_impl` registrations with framework `'inventory'` / `'linkme'`.
|
|
35
|
+
*
|
|
36
|
+
* Out of scope:
|
|
37
|
+
* - Subapp mounting (`app.use('/api', subApp)`) handler resolution.
|
|
38
|
+
* - Cross-file trait → impl resolution scoped by `Cargo.toml` reachability
|
|
39
|
+
* (file-local impls only at extraction time; project-level resolution is
|
|
40
|
+
* deferred to a later cross-file pass).
|
|
23
41
|
*/
|
|
24
42
|
import type { Tree } from 'web-tree-sitter';
|
|
25
43
|
import type { RuntimeRegistration, ImportInfo } from '../../types/index.js';
|
|
@@ -28,7 +46,9 @@ import type { SupportedLanguage } from '../parser.js';
|
|
|
28
46
|
/**
|
|
29
47
|
* Extract runtime-registration patterns from a parsed file.
|
|
30
48
|
*
|
|
31
|
-
*
|
|
49
|
+
* Phase 1 covers JavaScript/TypeScript. Phase 2 adds Python decorators.
|
|
50
|
+
* Phase 3 adds Rust trait dispatch (`impl Trait for Type`, `inventory::submit!`,
|
|
51
|
+
* `#[linkme::distributed_slice]`). Returns `[]` for any other language.
|
|
32
52
|
*/
|
|
33
53
|
export declare function extractRuntimeRegistrations(tree: Tree, cache: NodeCache | undefined, language: SupportedLanguage | string, imports?: ImportInfo[]): RuntimeRegistration[];
|
|
34
54
|
//# sourceMappingURL=runtime-registrations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-registrations.d.ts","sourceRoot":"","sources":["../../../src/core/extractors/runtime-registrations.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"runtime-registrations.d.ts","sourceRoot":"","sources":["../../../src/core/extractors/runtime-registrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;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;;;;;;GAMG;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,CAWvB"}
|