projscan 1.1.1 → 1.2.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/README.md +9 -5
- package/dist/cli/commands/coverage.js +4 -0
- package/dist/cli/commands/coverage.js.map +1 -1
- package/dist/cli/commands/prDiff.js +4 -0
- package/dist/cli/commands/prDiff.js.map +1 -1
- package/dist/core/ast.d.ts +14 -0
- package/dist/core/ast.js +30 -8
- package/dist/core/ast.js.map +1 -1
- package/dist/core/codeGraph.js +68 -3
- package/dist/core/codeGraph.js.map +1 -1
- package/dist/core/languages/LanguageAdapter.d.ts +1 -1
- package/dist/core/languages/csharpAdapter.d.ts +2 -0
- package/dist/core/languages/csharpAdapter.js +148 -0
- package/dist/core/languages/csharpAdapter.js.map +1 -0
- package/dist/core/languages/csharpCallSites.d.ts +22 -0
- package/dist/core/languages/csharpCallSites.js +73 -0
- package/dist/core/languages/csharpCallSites.js.map +1 -0
- package/dist/core/languages/csharpCyclomatic.d.ts +23 -0
- package/dist/core/languages/csharpCyclomatic.js +61 -0
- package/dist/core/languages/csharpCyclomatic.js.map +1 -0
- package/dist/core/languages/csharpExports.d.ts +13 -0
- package/dist/core/languages/csharpExports.js +69 -0
- package/dist/core/languages/csharpExports.js.map +1 -0
- package/dist/core/languages/csharpFunctions.d.ts +24 -0
- package/dist/core/languages/csharpFunctions.js +170 -0
- package/dist/core/languages/csharpFunctions.js.map +1 -0
- package/dist/core/languages/csharpImports.d.ts +30 -0
- package/dist/core/languages/csharpImports.js +90 -0
- package/dist/core/languages/csharpImports.js.map +1 -0
- package/dist/core/languages/goFunctions.js +31 -7
- package/dist/core/languages/goFunctions.js.map +1 -1
- package/dist/core/languages/javaFunctions.js +12 -5
- package/dist/core/languages/javaFunctions.js.map +1 -1
- package/dist/core/languages/phpAdapter.d.ts +2 -0
- package/dist/core/languages/phpAdapter.js +149 -0
- package/dist/core/languages/phpAdapter.js.map +1 -0
- package/dist/core/languages/phpCallSites.d.ts +21 -0
- package/dist/core/languages/phpCallSites.js +56 -0
- package/dist/core/languages/phpCallSites.js.map +1 -0
- package/dist/core/languages/phpCyclomatic.d.ts +27 -0
- package/dist/core/languages/phpCyclomatic.js +69 -0
- package/dist/core/languages/phpCyclomatic.js.map +1 -0
- package/dist/core/languages/phpExports.d.ts +13 -0
- package/dist/core/languages/phpExports.js +58 -0
- package/dist/core/languages/phpExports.js.map +1 -0
- package/dist/core/languages/phpFunctions.d.ts +25 -0
- package/dist/core/languages/phpFunctions.js +144 -0
- package/dist/core/languages/phpFunctions.js.map +1 -0
- package/dist/core/languages/phpImports.d.ts +31 -0
- package/dist/core/languages/phpImports.js +142 -0
- package/dist/core/languages/phpImports.js.map +1 -0
- package/dist/core/languages/phpManifests.d.ts +31 -0
- package/dist/core/languages/phpManifests.js +89 -0
- package/dist/core/languages/phpManifests.js.map +1 -0
- package/dist/core/languages/pythonFunctions.js +34 -8
- package/dist/core/languages/pythonFunctions.js.map +1 -1
- package/dist/core/languages/registry.js +3 -1
- package/dist/core/languages/registry.js.map +1 -1
- package/dist/core/languages/rubyFunctions.js +37 -5
- package/dist/core/languages/rubyFunctions.js.map +1 -1
- package/dist/core/languages/rustFunctions.js +44 -8
- package/dist/core/languages/rustFunctions.js.map +1 -1
- package/dist/core/languages/treeSitterLoader.js +3 -1
- package/dist/core/languages/treeSitterLoader.js.map +1 -1
- package/dist/grammars/tree-sitter-c_sharp.wasm +0 -0
- package/dist/grammars/tree-sitter-php.wasm +0 -0
- package/dist/reporters/htmlReporter.d.ts +3 -1
- package/dist/reporters/htmlReporter.js +86 -0
- package/dist/reporters/htmlReporter.js.map +1 -1
- package/dist/tool-manifest.json +2 -2
- package/package.json +4 -2
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
const PHP_DECISION_NODES = new Set([
|
|
2
|
+
'if_statement',
|
|
3
|
+
'else_if_clause',
|
|
4
|
+
'elseif_clause',
|
|
5
|
+
'for_statement',
|
|
6
|
+
'foreach_statement',
|
|
7
|
+
'while_statement',
|
|
8
|
+
'do_statement',
|
|
9
|
+
'catch_clause',
|
|
10
|
+
'conditional_expression',
|
|
11
|
+
]);
|
|
12
|
+
/**
|
|
13
|
+
* Per-function McCabe CC for PHP. Walks `function_definition` (free
|
|
14
|
+
* functions) and `method_declaration` nodes. Methods inside `class Foo`
|
|
15
|
+
* are named `Foo.method`; methods in interfaces/traits/enums use the
|
|
16
|
+
* declaring type's name. Anonymous functions and closures are not
|
|
17
|
+
* extracted as separate functions; their CC folds into the enclosing
|
|
18
|
+
* function.
|
|
19
|
+
*/
|
|
20
|
+
export function extractPhpFunctions(root) {
|
|
21
|
+
const out = [];
|
|
22
|
+
walk(root, null, out);
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
function walk(node, ownerName, out) {
|
|
26
|
+
// Containers that scope methods by their type name.
|
|
27
|
+
if (node.type === 'class_declaration' ||
|
|
28
|
+
node.type === 'interface_declaration' ||
|
|
29
|
+
node.type === 'trait_declaration' ||
|
|
30
|
+
node.type === 'enum_declaration') {
|
|
31
|
+
const nameNode = node.childForFieldName ? node.childForFieldName('name') : findChild(node, 'name');
|
|
32
|
+
const cls = nameNode?.text ?? ownerName;
|
|
33
|
+
const body = node.childForFieldName ? node.childForFieldName('body') : findChild(node, 'declaration_list');
|
|
34
|
+
if (body) {
|
|
35
|
+
for (const child of body.namedChildren)
|
|
36
|
+
walk(child, cls, out);
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
// namespace_definition wraps everything in a namespace { ... } block; descend.
|
|
41
|
+
if (node.type === 'namespace_definition') {
|
|
42
|
+
for (const child of node.namedChildren)
|
|
43
|
+
walk(child, ownerName, out);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (node.type === 'declaration_list' || node.type === 'compound_statement') {
|
|
47
|
+
for (const child of node.namedChildren)
|
|
48
|
+
walk(child, ownerName, out);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (node.type === 'function_definition' || node.type === 'method_declaration') {
|
|
52
|
+
const nameNode = node.childForFieldName ? node.childForFieldName('name') : findChild(node, 'name');
|
|
53
|
+
const baseName = nameNode?.text ?? '<anonymous>';
|
|
54
|
+
const fnName = ownerName && node.type === 'method_declaration' ? `${ownerName}.${baseName}` : baseName;
|
|
55
|
+
const line = node.startPosition.row + 1;
|
|
56
|
+
const endLine = node.endPosition.row + 1;
|
|
57
|
+
const { cc, callSites } = analyzeBody(node);
|
|
58
|
+
out.push({ name: fnName, line, endLine, cyclomaticComplexity: cc, callSites });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
for (const child of node.namedChildren)
|
|
62
|
+
walk(child, ownerName, out);
|
|
63
|
+
}
|
|
64
|
+
function analyzeBody(fnNode) {
|
|
65
|
+
let count = 0;
|
|
66
|
+
const calls = new Set();
|
|
67
|
+
const body = fnNode.childForFieldName ? fnNode.childForFieldName('body') : findChild(fnNode, 'compound_statement');
|
|
68
|
+
if (!body)
|
|
69
|
+
return { cc: 1, callSites: [] };
|
|
70
|
+
walkSkipNested(body, (n) => {
|
|
71
|
+
if (PHP_DECISION_NODES.has(n.type)) {
|
|
72
|
+
count++;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (n.type === 'case_statement') {
|
|
76
|
+
if (!/^\s*default\b/.test(n.text))
|
|
77
|
+
count++;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (n.type === 'match_condition_list') {
|
|
81
|
+
if (!/^\s*default\b/.test(n.text))
|
|
82
|
+
count++;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (n.type === 'binary_expression') {
|
|
86
|
+
const t = n.text;
|
|
87
|
+
if (/(\s|^)(\|\||&&|\?\?)(\s|$)/.test(t))
|
|
88
|
+
count++;
|
|
89
|
+
else if (/\b(and|or)\b/.test(t))
|
|
90
|
+
count++;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (n.type === 'function_call_expression' ||
|
|
94
|
+
n.type === 'member_call_expression' ||
|
|
95
|
+
n.type === 'nullsafe_member_call_expression' ||
|
|
96
|
+
n.type === 'scoped_call_expression') {
|
|
97
|
+
const name = phpCalleeName(n);
|
|
98
|
+
if (name)
|
|
99
|
+
calls.add(name);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
return { cc: count + 1, callSites: [...calls] };
|
|
103
|
+
}
|
|
104
|
+
function phpCalleeName(callNode) {
|
|
105
|
+
const target = callNode.type === 'function_call_expression'
|
|
106
|
+
? (callNode.childForFieldName ? callNode.childForFieldName('function') : null)
|
|
107
|
+
: (callNode.childForFieldName ? callNode.childForFieldName('name') : null);
|
|
108
|
+
if (!target)
|
|
109
|
+
return null;
|
|
110
|
+
switch (target.type) {
|
|
111
|
+
case 'name':
|
|
112
|
+
case 'variable_name':
|
|
113
|
+
return target.text.replace(/^\$/, '');
|
|
114
|
+
case 'qualified_name': {
|
|
115
|
+
let last = null;
|
|
116
|
+
for (const c of target.namedChildren)
|
|
117
|
+
if (c.type === 'name')
|
|
118
|
+
last = c;
|
|
119
|
+
return last ? last.text : null;
|
|
120
|
+
}
|
|
121
|
+
default:
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function walkSkipNested(node, visit) {
|
|
126
|
+
visit(node);
|
|
127
|
+
for (const child of node.namedChildren) {
|
|
128
|
+
if (child.type === 'function_definition' ||
|
|
129
|
+
child.type === 'method_declaration' ||
|
|
130
|
+
child.type === 'anonymous_function_creation_expression' ||
|
|
131
|
+
child.type === 'arrow_function') {
|
|
132
|
+
// Nested fn: skip so its decisions don't double-count into the parent.
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
walkSkipNested(child, visit);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function findChild(node, type) {
|
|
139
|
+
for (const c of node.namedChildren)
|
|
140
|
+
if (c.type === type)
|
|
141
|
+
return c;
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=phpFunctions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phpFunctions.js","sourceRoot":"","sources":["../../../src/core/languages/phpFunctions.ts"],"names":[],"mappings":"AAWA,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,cAAc;IACd,gBAAgB;IAChB,eAAe;IACf,eAAe;IACf,mBAAmB;IACnB,iBAAiB;IACjB,cAAc;IACd,cAAc;IACd,wBAAwB;CACzB,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IACtB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,IAAI,CAAC,IAAY,EAAE,SAAwB,EAAE,GAAmB;IACvE,oDAAoD;IACpD,IACE,IAAI,CAAC,IAAI,KAAK,mBAAmB;QACjC,IAAI,CAAC,IAAI,KAAK,uBAAuB;QACrC,IAAI,CAAC,IAAI,KAAK,mBAAmB;QACjC,IAAI,CAAC,IAAI,KAAK,kBAAkB,EAChC,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACnG,MAAM,GAAG,GAAG,QAAQ,EAAE,IAAI,IAAI,SAAS,CAAC;QACxC,MAAM,IAAI,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC;QAC3G,IAAI,IAAI,EAAE,CAAC;YACT,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa;gBAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAChE,CAAC;QACD,OAAO;IACT,CAAC;IAED,+EAA+E;IAC/E,IAAI,IAAI,CAAC,IAAI,KAAK,sBAAsB,EAAE,CAAC;QACzC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa;YAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QACpE,OAAO;IACT,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,kBAAkB,IAAI,IAAI,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;QAC3E,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa;YAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QACpE,OAAO;IACT,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,KAAK,qBAAqB,IAAI,IAAI,CAAC,IAAI,KAAK,oBAAoB,EAAE,CAAC;QAC9E,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACnG,MAAM,QAAQ,GAAG,QAAQ,EAAE,IAAI,IAAI,aAAa,CAAC;QACjD,MAAM,MAAM,GAAG,SAAS,IAAI,IAAI,CAAC,IAAI,KAAK,oBAAoB,CAAC,CAAC,CAAC,GAAG,SAAS,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;QACvG,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,GAAG,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,GAAG,CAAC,CAAC;QACzC,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAC5C,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,oBAAoB,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QAC/E,OAAO;IACT,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa;QAAE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,WAAW,CAAC,MAAc;IACjC,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,MAAM,IAAI,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC,MAAM,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IACnH,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;IAC3C,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;QACzB,IAAI,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,KAAK,EAAE,CAAC;YACR,OAAO;QACT,CAAC;QACD,IAAI,CAAC,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;YAChC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;gBAAE,KAAK,EAAE,CAAC;YAC3C,OAAO;QACT,CAAC;QACD,IAAI,CAAC,CAAC,IAAI,KAAK,sBAAsB,EAAE,CAAC;YACtC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;gBAAE,KAAK,EAAE,CAAC;YAC3C,OAAO;QACT,CAAC;QACD,IAAI,CAAC,CAAC,IAAI,KAAK,mBAAmB,EAAE,CAAC;YACnC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACjB,IAAI,4BAA4B,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,KAAK,EAAE,CAAC;iBAC7C,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,KAAK,EAAE,CAAC;YACzC,OAAO;QACT,CAAC;QACD,IACE,CAAC,CAAC,IAAI,KAAK,0BAA0B;YACrC,CAAC,CAAC,IAAI,KAAK,wBAAwB;YACnC,CAAC,CAAC,IAAI,KAAK,iCAAiC;YAC5C,CAAC,CAAC,IAAI,KAAK,wBAAwB,EACnC,CAAC;YACD,MAAM,IAAI,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;YAC9B,IAAI,IAAI;gBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,EAAE,EAAE,KAAK,GAAG,CAAC,EAAE,SAAS,EAAE,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC;AAClD,CAAC;AAED,SAAS,aAAa,CAAC,QAAgB;IACrC,MAAM,MAAM,GACV,QAAQ,CAAC,IAAI,KAAK,0BAA0B;QAC1C,CAAC,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC9E,CAAC,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC/E,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC;QACZ,KAAK,eAAe;YAClB,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACxC,KAAK,gBAAgB,CAAC,CAAC,CAAC;YACtB,IAAI,IAAI,GAAkB,IAAI,CAAC;YAC/B,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,aAAa;gBAAE,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM;oBAAE,IAAI,GAAG,CAAC,CAAC;YACtE,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;QACjC,CAAC;QACD;YACE,OAAO,IAAI,CAAC;IAChB,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,IAAY,EAAE,KAA0B;IAC9D,KAAK,CAAC,IAAI,CAAC,CAAC;IACZ,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;QACvC,IACE,KAAK,CAAC,IAAI,KAAK,qBAAqB;YACpC,KAAK,CAAC,IAAI,KAAK,oBAAoB;YACnC,KAAK,CAAC,IAAI,KAAK,wCAAwC;YACvD,KAAK,CAAC,IAAI,KAAK,gBAAgB,EAC/B,CAAC;YACD,uEAAuE;YACvE,SAAS;QACX,CAAC;QACD,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,IAAY,EAAE,IAAY;IAC3C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,aAAa;QAAE,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO,CAAC,CAAC;IAClE,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AstImport } from '../ast.js';
|
|
2
|
+
interface TsNode {
|
|
3
|
+
type: string;
|
|
4
|
+
text: string;
|
|
5
|
+
startPosition: {
|
|
6
|
+
row: number;
|
|
7
|
+
column: number;
|
|
8
|
+
};
|
|
9
|
+
namedChildren: TsNode[];
|
|
10
|
+
childForFieldName?: (name: string) => TsNode | null;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Extract PHP `use` statements from a tree-sitter-php AST.
|
|
14
|
+
*
|
|
15
|
+
* Handled forms:
|
|
16
|
+
* use Foo\Bar; → "Foo\\Bar"
|
|
17
|
+
* use Foo\Bar as Baz; → "Foo\\Bar", alias "Baz"
|
|
18
|
+
* use Foo\{Bar, Baz, Qux as Q}; → three imports
|
|
19
|
+
* use function Foo\some_fn; → "Foo\\some_fn", function-only
|
|
20
|
+
* use const Foo\SOME_CONST; → "Foo\\SOME_CONST", const-only
|
|
21
|
+
*
|
|
22
|
+
* Also handled in a lenient way:
|
|
23
|
+
* require / require_once / include / include_once 'path/to/file.php';
|
|
24
|
+
* → "path/to/file.php" (the resolver tries to land it on a repo file).
|
|
25
|
+
*
|
|
26
|
+
* NOT handled:
|
|
27
|
+
* - Dynamic require / include (`require $var`)
|
|
28
|
+
* - `extract()` / `class_exists()` reflective imports
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractPhpImports(root: TsNode): AstImport[];
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract PHP `use` statements from a tree-sitter-php AST.
|
|
3
|
+
*
|
|
4
|
+
* Handled forms:
|
|
5
|
+
* use Foo\Bar; → "Foo\\Bar"
|
|
6
|
+
* use Foo\Bar as Baz; → "Foo\\Bar", alias "Baz"
|
|
7
|
+
* use Foo\{Bar, Baz, Qux as Q}; → three imports
|
|
8
|
+
* use function Foo\some_fn; → "Foo\\some_fn", function-only
|
|
9
|
+
* use const Foo\SOME_CONST; → "Foo\\SOME_CONST", const-only
|
|
10
|
+
*
|
|
11
|
+
* Also handled in a lenient way:
|
|
12
|
+
* require / require_once / include / include_once 'path/to/file.php';
|
|
13
|
+
* → "path/to/file.php" (the resolver tries to land it on a repo file).
|
|
14
|
+
*
|
|
15
|
+
* NOT handled:
|
|
16
|
+
* - Dynamic require / include (`require $var`)
|
|
17
|
+
* - `extract()` / `class_exists()` reflective imports
|
|
18
|
+
*/
|
|
19
|
+
export function extractPhpImports(root) {
|
|
20
|
+
const imports = [];
|
|
21
|
+
walk(root, (n) => {
|
|
22
|
+
if (n.type === 'namespace_use_declaration') {
|
|
23
|
+
collectUseDeclaration(n, imports);
|
|
24
|
+
}
|
|
25
|
+
else if (n.type === 'expression_statement') {
|
|
26
|
+
// require / include statements are expression statements wrapping an
|
|
27
|
+
// include_expression / require_expression node.
|
|
28
|
+
for (const child of n.namedChildren) {
|
|
29
|
+
if (child.type === 'include_expression' ||
|
|
30
|
+
child.type === 'include_once_expression' ||
|
|
31
|
+
child.type === 'require_expression' ||
|
|
32
|
+
child.type === 'require_once_expression') {
|
|
33
|
+
collectIncludeStatement(child, imports);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return imports;
|
|
39
|
+
}
|
|
40
|
+
function collectUseDeclaration(node, out) {
|
|
41
|
+
// Some grammars expose `function` / `const` as a child token; we don't
|
|
42
|
+
// distinguish those in the AstImport shape but we do parse the path.
|
|
43
|
+
const line = node.startPosition.row + 1;
|
|
44
|
+
// Find the qualifier (the `Foo\Bar` prefix) and any use-clauses or use-list.
|
|
45
|
+
// The grammar can produce one of:
|
|
46
|
+
// namespace_use_declaration > namespace_use_clause > qualified_name
|
|
47
|
+
// namespace_use_declaration > namespace_name (prefix) + namespace_use_group
|
|
48
|
+
// The group's prefix is a sibling of the group, not a child.
|
|
49
|
+
let groupPrefix = '';
|
|
50
|
+
for (const child of node.namedChildren) {
|
|
51
|
+
if (child.type === 'namespace_name' || child.type === 'qualified_name') {
|
|
52
|
+
groupPrefix = readPath(child);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const child of node.namedChildren) {
|
|
56
|
+
if (child.type === 'namespace_use_clause') {
|
|
57
|
+
const { source, alias } = extractClause(child);
|
|
58
|
+
if (source)
|
|
59
|
+
out.push(makeImport(source, alias, line));
|
|
60
|
+
}
|
|
61
|
+
else if (child.type === 'namespace_use_group') {
|
|
62
|
+
for (const groupChild of child.namedChildren) {
|
|
63
|
+
if (groupChild.type === 'namespace_use_clause' ||
|
|
64
|
+
groupChild.type === 'namespace_use_group_clause') {
|
|
65
|
+
const { source, alias } = extractClause(groupChild);
|
|
66
|
+
if (!source)
|
|
67
|
+
continue;
|
|
68
|
+
const full = groupPrefix ? `${groupPrefix}\\${source}` : source;
|
|
69
|
+
out.push(makeImport(full, alias, line));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function extractClause(node) {
|
|
76
|
+
let source = '';
|
|
77
|
+
let alias;
|
|
78
|
+
for (const c of node.namedChildren) {
|
|
79
|
+
if (c.type === 'qualified_name' || c.type === 'namespace_name' || c.type === 'name') {
|
|
80
|
+
if (!source)
|
|
81
|
+
source = readPath(c);
|
|
82
|
+
}
|
|
83
|
+
else if (c.type === 'namespace_aliasing_clause' || c.type === 'name') {
|
|
84
|
+
// alias clauses look like `as Baz` — the `name` after `as` is the alias.
|
|
85
|
+
// This branch fires for `name` only when source is already set.
|
|
86
|
+
const aliasName = node.childForFieldName ? node.childForFieldName('alias') : null;
|
|
87
|
+
if (aliasName)
|
|
88
|
+
alias = aliasName.text;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Field-based alias (the grammar's preferred shape).
|
|
92
|
+
if (!alias && node.childForFieldName) {
|
|
93
|
+
const a = node.childForFieldName('alias');
|
|
94
|
+
if (a)
|
|
95
|
+
alias = a.text;
|
|
96
|
+
}
|
|
97
|
+
return { source, alias };
|
|
98
|
+
}
|
|
99
|
+
function collectIncludeStatement(expr, out) {
|
|
100
|
+
// include_expression : `include 'path'` ; first non-include child is the
|
|
101
|
+
// path expression. Grammar variants put it as a `string` literal child.
|
|
102
|
+
const line = expr.startPosition.row + 1;
|
|
103
|
+
for (const c of expr.namedChildren) {
|
|
104
|
+
if (c.type === 'string' || c.type === 'encapsed_string') {
|
|
105
|
+
const stringContent = c.namedChildren.find((s) => s.type === 'string_content');
|
|
106
|
+
const raw = stringContent ? stringContent.text : c.text.replace(/^['"]|['"]$/g, '');
|
|
107
|
+
if (raw)
|
|
108
|
+
out.push({ source: raw, kind: 'static', specifiers: [], typeOnly: false, line });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function readPath(node) {
|
|
114
|
+
// qualified_name has children: namespace_name (or names) + name.
|
|
115
|
+
// Concatenate with backslashes.
|
|
116
|
+
const parts = [];
|
|
117
|
+
collectName(node, parts);
|
|
118
|
+
return parts.join('\\');
|
|
119
|
+
}
|
|
120
|
+
function collectName(node, out) {
|
|
121
|
+
if (node.type === 'name') {
|
|
122
|
+
out.push(node.text);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
for (const c of node.namedChildren)
|
|
126
|
+
collectName(c, out);
|
|
127
|
+
}
|
|
128
|
+
function makeImport(source, alias, line) {
|
|
129
|
+
return {
|
|
130
|
+
source,
|
|
131
|
+
kind: 'static',
|
|
132
|
+
specifiers: alias ? [alias] : [],
|
|
133
|
+
typeOnly: false,
|
|
134
|
+
line,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function walk(node, visit) {
|
|
138
|
+
visit(node);
|
|
139
|
+
for (const child of node.namedChildren)
|
|
140
|
+
walk(child, visit);
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=phpImports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phpImports.js","sourceRoot":"","sources":["../../../src/core/languages/phpImports.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { FileEntry } from '../../types.js';
|
|
2
|
+
export interface PhpProjectInfo {
|
|
3
|
+
/** Absolute directory containing composer.json. Imports resolve relative to its autoload roots. */
|
|
4
|
+
projectRoot: string;
|
|
5
|
+
/**
|
|
6
|
+
* PSR-4 namespace prefixes mapped to a relative source root. Example:
|
|
7
|
+
* `[{ prefix: 'App\\', root: 'src' }, { prefix: 'App\\Tests\\', root: 'tests' }]`.
|
|
8
|
+
* Resolution strategy: longest prefix match wins.
|
|
9
|
+
*/
|
|
10
|
+
psr4: Array<{
|
|
11
|
+
prefix: string;
|
|
12
|
+
root: string;
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* `autoload.classmap` entries (relative dirs / files). PSR-4 covers most
|
|
16
|
+
* modern PHP; classmap is older but still common in legacy code.
|
|
17
|
+
*/
|
|
18
|
+
classmap: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Find the closest composer.json and read its autoload section. Tries the
|
|
22
|
+
* repo root first, then walks up from any directory containing a `.php`
|
|
23
|
+
* file. Returns null when no composer.json exists; .php files outside any
|
|
24
|
+
* Composer project are valid (snippets, scratch files) but their `use`
|
|
25
|
+
* clauses can't be resolved to local files.
|
|
26
|
+
*
|
|
27
|
+
* Both `autoload` and `autoload-dev` are read so that test files importing
|
|
28
|
+
* from `App\Tests\` resolve. PSR-4 always wins over classmap when both
|
|
29
|
+
* could resolve a path.
|
|
30
|
+
*/
|
|
31
|
+
export declare function detectPhpProject(rootPath: string, files: FileEntry[]): Promise<PhpProjectInfo | null>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Find the closest composer.json and read its autoload section. Tries the
|
|
5
|
+
* repo root first, then walks up from any directory containing a `.php`
|
|
6
|
+
* file. Returns null when no composer.json exists; .php files outside any
|
|
7
|
+
* Composer project are valid (snippets, scratch files) but their `use`
|
|
8
|
+
* clauses can't be resolved to local files.
|
|
9
|
+
*
|
|
10
|
+
* Both `autoload` and `autoload-dev` are read so that test files importing
|
|
11
|
+
* from `App\Tests\` resolve. PSR-4 always wins over classmap when both
|
|
12
|
+
* could resolve a path.
|
|
13
|
+
*/
|
|
14
|
+
export async function detectPhpProject(rootPath, files) {
|
|
15
|
+
// 1) Try repo root.
|
|
16
|
+
const fromRoot = await readComposer(rootPath);
|
|
17
|
+
if (fromRoot)
|
|
18
|
+
return fromRoot;
|
|
19
|
+
// 2) Walk up from .php file directories.
|
|
20
|
+
const candidates = new Set();
|
|
21
|
+
for (const f of files) {
|
|
22
|
+
if (!f.relativePath.endsWith('.php'))
|
|
23
|
+
continue;
|
|
24
|
+
let dir = path.posix.dirname(f.relativePath);
|
|
25
|
+
while (dir && dir !== '.' && dir !== '/') {
|
|
26
|
+
candidates.add(dir);
|
|
27
|
+
dir = path.posix.dirname(dir);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const sorted = [...candidates].sort((a, b) => a.length - b.length);
|
|
31
|
+
for (const dir of sorted) {
|
|
32
|
+
const project = await readComposer(path.join(rootPath, dir));
|
|
33
|
+
if (project)
|
|
34
|
+
return project;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
async function readComposer(dir) {
|
|
39
|
+
const p = path.join(dir, 'composer.json');
|
|
40
|
+
let raw;
|
|
41
|
+
try {
|
|
42
|
+
raw = await fs.readFile(p, 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = JSON.parse(raw);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const psr4 = [];
|
|
55
|
+
const classmap = [];
|
|
56
|
+
for (const key of ['autoload', 'autoload-dev']) {
|
|
57
|
+
const block = parsed[key];
|
|
58
|
+
if (!block || typeof block !== 'object')
|
|
59
|
+
continue;
|
|
60
|
+
const psr4Block = block['psr-4'];
|
|
61
|
+
if (psr4Block && typeof psr4Block === 'object') {
|
|
62
|
+
for (const [prefix, target] of Object.entries(psr4Block)) {
|
|
63
|
+
if (typeof target === 'string') {
|
|
64
|
+
psr4.push({ prefix, root: stripTrailingSlash(target) });
|
|
65
|
+
}
|
|
66
|
+
else if (Array.isArray(target)) {
|
|
67
|
+
for (const t of target) {
|
|
68
|
+
if (typeof t === 'string')
|
|
69
|
+
psr4.push({ prefix, root: stripTrailingSlash(t) });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const cm = block.classmap;
|
|
75
|
+
if (Array.isArray(cm)) {
|
|
76
|
+
for (const entry of cm)
|
|
77
|
+
if (typeof entry === 'string')
|
|
78
|
+
classmap.push(stripTrailingSlash(entry));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Sort PSR-4 entries by prefix length descending so longest-match resolution
|
|
82
|
+
// is a single pass.
|
|
83
|
+
psr4.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
84
|
+
return { projectRoot: dir, psr4, classmap };
|
|
85
|
+
}
|
|
86
|
+
function stripTrailingSlash(s) {
|
|
87
|
+
return s.endsWith('/') ? s.slice(0, -1) : s;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=phpManifests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phpManifests.js","sourceRoot":"","sources":["../../../src/core/languages/phpManifests.ts"],"names":[],"mappings":"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"}
|
|
@@ -29,8 +29,8 @@ function walk(node, className, out) {
|
|
|
29
29
|
const qualifiedName = className ? `${className}.${fnName}` : fnName;
|
|
30
30
|
const line = node.startPosition.row + 1;
|
|
31
31
|
const endLine = node.endPosition.row + 1;
|
|
32
|
-
const cc =
|
|
33
|
-
out.push({ name: qualifiedName, line, endLine, cyclomaticComplexity: cc });
|
|
32
|
+
const { cc, callSites } = analyzeBody(node);
|
|
33
|
+
out.push({ name: qualifiedName, line, endLine, cyclomaticComplexity: cc, callSites });
|
|
34
34
|
// Recurse into the body to find nested functions only - the CC for THIS
|
|
35
35
|
// function already correctly excluded them via the body-walker.
|
|
36
36
|
const body = node.childForFieldName ? node.childForFieldName('body') : null;
|
|
@@ -56,19 +56,45 @@ function walk(node, className, out) {
|
|
|
56
56
|
walk(child, className, out);
|
|
57
57
|
}
|
|
58
58
|
/**
|
|
59
|
-
* Count decision points inside a function body,
|
|
60
|
-
*
|
|
59
|
+
* Count decision points and collect call sites inside a function body,
|
|
60
|
+
* skipping nested function / class definitions.
|
|
61
61
|
*/
|
|
62
|
-
function
|
|
62
|
+
function analyzeBody(fnNode) {
|
|
63
63
|
let count = 0;
|
|
64
|
+
const calls = new Set();
|
|
64
65
|
const body = fnNode.childForFieldName ? fnNode.childForFieldName('body') : null;
|
|
65
66
|
if (!body)
|
|
66
|
-
return 1;
|
|
67
|
+
return { cc: 1, callSites: [] };
|
|
67
68
|
walkSkipNested(body, (n) => {
|
|
68
|
-
if (PY_DECISION_NODES.has(n.type))
|
|
69
|
+
if (PY_DECISION_NODES.has(n.type)) {
|
|
69
70
|
count++;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (n.type === 'call') {
|
|
74
|
+
const fn = n.childForFieldName ? n.childForFieldName('function') : (n.namedChildren[0] ?? null);
|
|
75
|
+
const name = pyCalleeName(fn);
|
|
76
|
+
if (name)
|
|
77
|
+
calls.add(name);
|
|
78
|
+
}
|
|
70
79
|
});
|
|
71
|
-
return count + 1;
|
|
80
|
+
return { cc: count + 1, callSites: [...calls] };
|
|
81
|
+
}
|
|
82
|
+
function pyCalleeName(node) {
|
|
83
|
+
if (!node)
|
|
84
|
+
return null;
|
|
85
|
+
switch (node.type) {
|
|
86
|
+
case 'identifier':
|
|
87
|
+
return node.text;
|
|
88
|
+
case 'attribute': {
|
|
89
|
+
const attr = node.childForFieldName ? node.childForFieldName('attribute') : null;
|
|
90
|
+
if (attr)
|
|
91
|
+
return pyCalleeName(attr);
|
|
92
|
+
const named = node.namedChildren;
|
|
93
|
+
return named.length > 0 ? pyCalleeName(named[named.length - 1]) : null;
|
|
94
|
+
}
|
|
95
|
+
default:
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
72
98
|
}
|
|
73
99
|
function walkSkipNested(node, visit) {
|
|
74
100
|
visit(node);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pythonFunctions.js","sourceRoot":"","sources":["../../../src/core/languages/pythonFunctions.ts"],"names":[],"mappings":"AAWA,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC;AAE3D,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,cAAc;IACd,aAAa;IACb,eAAe;IACf,iBAAiB;IACjB,eAAe;IACf,wBAAwB;IACxB,eAAe;IACf,WAAW;IACX,aAAa;IACb,kBAAkB;CACnB,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,MAAM,GAAG,GAAmB,EAAE,CAAC;IAC/B,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;IACtB,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,IAAI,CAAC,IAAY,EAAE,SAAwB,EAAE,GAAmB;IACvE,IAAI,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;QACzG,MAAM,MAAM,GAAG,QAAQ,EAAE,IAAI,IAAI,aAAa,CAAC;QAC/C,MAAM,aAAa,GAAG,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,IAAI,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;QACpE,MAAM,IAAI,GAAG,IAAI,CAAC,aAAa,CAAC,GAAG,GAAG,CAAC,CAAC;QACxC,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,GAAG,CAAC,CAAC;QACzC,MAAM,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"pythonFunctions.js","sourceRoot":"","sources":["../../../src/core/languages/pythonFunctions.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,9 @@ import { goAdapter } from './goAdapter.js';
|
|
|
5
5
|
import { javaAdapter } from './javaAdapter.js';
|
|
6
6
|
import { rubyAdapter } from './rubyAdapter.js';
|
|
7
7
|
import { rustAdapter } from './rustAdapter.js';
|
|
8
|
-
|
|
8
|
+
import { phpAdapter } from './phpAdapter.js';
|
|
9
|
+
import { csharpAdapter } from './csharpAdapter.js';
|
|
10
|
+
const adapters = [javascriptAdapter, pythonAdapter, goAdapter, javaAdapter, rubyAdapter, rustAdapter, phpAdapter, csharpAdapter];
|
|
9
11
|
const byExtension = new Map();
|
|
10
12
|
function rebuildIndex() {
|
|
11
13
|
byExtension.clear();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/core/languages/registry.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/core/languages/registry.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD,MAAM,QAAQ,GAAsB,CAAC,iBAAiB,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,CAAC,CAAC;AACpJ,MAAM,WAAW,GAAG,IAAI,GAAG,EAA2B,CAAC;AAEvD,SAAS,YAAY;IACnB,WAAW,CAAC,KAAK,EAAE,CAAC;IACpB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACrC,WAAW,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;AACH,CAAC;AACD,YAAY,EAAE,CAAC;AAEf,MAAM,UAAU,eAAe,CAAC,OAAwB;IACtD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACvB,YAAY,EAAE,CAAC;IACjB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IACjD,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|
|
@@ -40,18 +40,19 @@ function walk(node, className, out) {
|
|
|
40
40
|
const fnName = className ? `${className}.${baseName}` : baseName;
|
|
41
41
|
const line = node.startPosition.row + 1;
|
|
42
42
|
const endLine = node.endPosition.row + 1;
|
|
43
|
-
const cc =
|
|
44
|
-
out.push({ name: fnName, line, endLine, cyclomaticComplexity: cc });
|
|
43
|
+
const { cc, callSites } = analyzeBody(node);
|
|
44
|
+
out.push({ name: fnName, line, endLine, cyclomaticComplexity: cc, callSites });
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
for (const child of node.namedChildren)
|
|
48
48
|
walk(child, className, out);
|
|
49
49
|
}
|
|
50
|
-
function
|
|
50
|
+
function analyzeBody(fnNode) {
|
|
51
51
|
let count = 0;
|
|
52
|
+
const calls = new Set();
|
|
52
53
|
const body = fnNode.childForFieldName ? fnNode.childForFieldName('body') : null;
|
|
53
54
|
if (!body)
|
|
54
|
-
return 1;
|
|
55
|
+
return { cc: 1, callSites: [] };
|
|
55
56
|
walkSkipNested(body, (n) => {
|
|
56
57
|
if (RUBY_DECISION_NODES.has(n.type)) {
|
|
57
58
|
count++;
|
|
@@ -60,9 +61,40 @@ function countDecisions(fnNode) {
|
|
|
60
61
|
if (n.type === 'binary') {
|
|
61
62
|
if (/(\s|^)(\|\||&&)(\s|$)/.test(n.text) || /\b(and|or)\b/.test(n.text))
|
|
62
63
|
count++;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (n.type === 'call' || n.type === 'method_call') {
|
|
67
|
+
const name = rubyCalleeName(n);
|
|
68
|
+
if (name)
|
|
69
|
+
calls.add(name);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (n.type === 'identifier') {
|
|
73
|
+
// Ruby allows calling methods without parens or receiver: `foo` is a
|
|
74
|
+
// call. Distinguishing identifier-as-call vs identifier-as-local is
|
|
75
|
+
// beyond what we can do without scope tracking; skip the bare-id case
|
|
76
|
+
// and rely on `call` / `method_call` shapes (which cover `foo()`,
|
|
77
|
+
// `obj.foo`, and `foo arg` per the grammar).
|
|
78
|
+
return;
|
|
63
79
|
}
|
|
64
80
|
});
|
|
65
|
-
return count + 1;
|
|
81
|
+
return { cc: count + 1, callSites: [...calls] };
|
|
82
|
+
}
|
|
83
|
+
function rubyCalleeName(node) {
|
|
84
|
+
// tree-sitter-ruby's `call` has a `method` field for the called name.
|
|
85
|
+
const m = node.childForFieldName ? node.childForFieldName('method') : null;
|
|
86
|
+
if (m) {
|
|
87
|
+
if (m.type === 'identifier' || m.type === 'constant')
|
|
88
|
+
return m.text;
|
|
89
|
+
return m.text;
|
|
90
|
+
}
|
|
91
|
+
// Fallback: last identifier-bearing child.
|
|
92
|
+
for (let i = node.namedChildren.length - 1; i >= 0; i--) {
|
|
93
|
+
const c = node.namedChildren[i];
|
|
94
|
+
if (c.type === 'identifier' || c.type === 'constant')
|
|
95
|
+
return c.text;
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
66
98
|
}
|
|
67
99
|
function walkSkipNested(node, visit) {
|
|
68
100
|
visit(node);
|