@titan-design/code-graph 0.5.0 → 0.6.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 +7 -4
- package/dist/index.d.ts +5 -5
- package/dist/index.js +217 -41
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -27,8 +27,8 @@ In: the parser (tree-sitter WASM for TypeScript, TSX and Python, since moved to
|
|
|
27
27
|
`@titan-design/code-parser` and still re-exported here), the walk, the ts-morph
|
|
28
28
|
extractor and its symbol layer, role classification, generated-file detection, id aliasing
|
|
29
29
|
across git renames, the three-tier incremental reuse, and the metrics computed at index time
|
|
30
|
-
(degree, utilization, loc, cyclomatic, cognitive, nesting, class count, lcom4, per
|
|
31
|
-
|
|
30
|
+
(degree, utilization, loc, cyclomatic, cognitive, nesting, class count, lcom4, and per
|
|
31
|
+
symbol `symbol_cognitive`, `symbol_cyclomatic`, `symbol_loc` and `symbol_max_nesting`). `lcom.ts` came along despite being an analysis: `source-metrics.ts` calls it
|
|
32
32
|
directly and lcom4 is a pure function of a file's bytes, so it belongs with the metrics that
|
|
33
33
|
carry forward under reuse.
|
|
34
34
|
|
|
@@ -307,8 +307,11 @@ Dead-code and growth-risk metrics are computed at index time, like the source me
|
|
|
307
307
|
carry forward for unchanged files. Both are sparse: a file gets a row only when a count is
|
|
308
308
|
above zero.
|
|
309
309
|
|
|
310
|
-
- Dead code, TypeScript
|
|
311
|
-
or `continue` in the same block), `unused_locals`, and `unused_params`
|
|
310
|
+
- Dead code, TypeScript and Python: `unreachable_statements` (after a `return`, `throw` or
|
|
311
|
+
`raise`, `break` or `continue` in the same block), `unused_locals`, and `unused_params`
|
|
312
|
+
(trailing run only). Python skips `self`, `cls`, `_`-prefixed names, `global` and
|
|
313
|
+
`nonlocal` names, and the parameters of stub bodies such as `@overload` signatures; a
|
|
314
|
+
`*args` or `**kwargs` ends the trailing run.
|
|
312
315
|
- Growth risk, TypeScript and Python: `loop_depth` (at 2 or more), `recursive_functions`,
|
|
313
316
|
and `search_in_loop` (`.includes`, `.find` and similar inside a loop). These are smells,
|
|
314
317
|
not complexity bounds. Recursion and search match TypeScript call nodes only, so Python
|
package/dist/index.d.ts
CHANGED
|
@@ -205,7 +205,7 @@ declare function runPrune(store: CodeGraphStore, options?: PruneOptions & {
|
|
|
205
205
|
* index version is never reused, so a change to node/edge shape or to a metric's
|
|
206
206
|
* value for the same bytes can never be carried forward from an incompatible graph.
|
|
207
207
|
*/
|
|
208
|
-
declare const INDEX_VERSION = "0.
|
|
208
|
+
declare const INDEX_VERSION = "0.16.0";
|
|
209
209
|
interface IndexOptions {
|
|
210
210
|
/** Roots to walk. Node ids are still rooted at the git toplevel, so importers across roots share an id space. */
|
|
211
211
|
paths: string[];
|
|
@@ -779,10 +779,10 @@ declare function buildIndexerMetrics(input: IndexerMetricsInput): GraphMetric[];
|
|
|
779
779
|
*/
|
|
780
780
|
declare const DEAD_CODE_METRIC_NAMES: ReadonlySet<string>;
|
|
781
781
|
/**
|
|
782
|
-
* Per-file dead-code metrics for TypeScript
|
|
783
|
-
*
|
|
784
|
-
*
|
|
785
|
-
*
|
|
782
|
+
* Per-file dead-code metrics for TypeScript and Python files (Python since
|
|
783
|
+
* TP-318, in dead-code-python.ts). Emitted sparsely — only when a count is > 0 —
|
|
784
|
+
* so a clean file adds no rows (a full index and an incremental re-index
|
|
785
|
+
* therefore produce the identical metric set). Other languages are skipped.
|
|
786
786
|
*/
|
|
787
787
|
declare function computeDeadCodeMetrics(files: readonly ParsedFile[], fileIdOf: (filePath: string) => string): GraphMetric[];
|
|
788
788
|
|
package/dist/index.js
CHANGED
|
@@ -1937,6 +1937,36 @@ function areLinked(a, b) {
|
|
|
1937
1937
|
return false;
|
|
1938
1938
|
}
|
|
1939
1939
|
|
|
1940
|
+
// src/symbol-metrics.ts
|
|
1941
|
+
var SYMBOL_METRICS = [
|
|
1942
|
+
{ name: "symbol_cognitive", stat: "cognitive", unit: "count" },
|
|
1943
|
+
{ name: "symbol_cyclomatic", stat: "cyclomatic", unit: "count" },
|
|
1944
|
+
{ name: "symbol_loc", stat: "loc", unit: "lines" },
|
|
1945
|
+
{ name: "symbol_max_nesting", stat: "nestingDepth", unit: "count" }
|
|
1946
|
+
];
|
|
1947
|
+
var SYMBOL_METRIC_NAMES = SYMBOL_METRICS.map((m) => m.name);
|
|
1948
|
+
function symbolMetrics(fileId2, stats, symbolNames) {
|
|
1949
|
+
if (symbolNames.size === 0) return [];
|
|
1950
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1951
|
+
for (const s of stats) {
|
|
1952
|
+
if (!s.name || !symbolNames.has(s.name)) continue;
|
|
1953
|
+
const prev = byName.get(s.name);
|
|
1954
|
+
if (!prev) {
|
|
1955
|
+
byName.set(s.name, { ...s });
|
|
1956
|
+
continue;
|
|
1957
|
+
}
|
|
1958
|
+
for (const { stat } of SYMBOL_METRICS) prev[stat] = Math.max(prev[stat], s[stat]);
|
|
1959
|
+
}
|
|
1960
|
+
const out = [];
|
|
1961
|
+
for (const [name, values] of byName) {
|
|
1962
|
+
const nodeId = symbolId(fileId2, name);
|
|
1963
|
+
for (const m of SYMBOL_METRICS) {
|
|
1964
|
+
out.push({ nodeId, name: m.name, value: values[m.stat], unit: m.unit });
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
return out;
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1940
1970
|
// src/source-metrics.ts
|
|
1941
1971
|
var TS_FUNCTION_TYPES = /* @__PURE__ */ new Set([
|
|
1942
1972
|
"function_declaration",
|
|
@@ -1986,12 +2016,11 @@ var SOURCE_METRIC_NAMES = /* @__PURE__ */ new Set([
|
|
|
1986
2016
|
"max_nesting_depth",
|
|
1987
2017
|
"class_count",
|
|
1988
2018
|
"lcom4_max",
|
|
1989
|
-
// Per-symbol complexity (C-58
|
|
2019
|
+
// Per-symbol complexity, loc and nesting (C-58, C-64, TP-317), keyed
|
|
1990
2020
|
// to `symbol` node ids. Source-local like the file-level metrics above, so an
|
|
1991
2021
|
// unchanged file carries them forward — but their nodeId is `<fileId>#<name>`,
|
|
1992
2022
|
// so the reuse basis buckets them under the symbol's parent file (incremental.ts).
|
|
1993
|
-
|
|
1994
|
-
"symbol_cyclomatic"
|
|
2023
|
+
...SYMBOL_METRIC_NAMES
|
|
1995
2024
|
]);
|
|
1996
2025
|
var EMPTY_NAMES = /* @__PURE__ */ new Set();
|
|
1997
2026
|
function computeSourceMetrics(files, fileIdOf, symbolNamesByFile2 = /* @__PURE__ */ new Map()) {
|
|
@@ -2045,30 +2074,10 @@ function metricsForFile(nodeId, file, symbolNames) {
|
|
|
2045
2074
|
unit: "count"
|
|
2046
2075
|
});
|
|
2047
2076
|
}
|
|
2048
|
-
out.push(...
|
|
2077
|
+
out.push(...symbolMetrics(nodeId, stats, symbolNames));
|
|
2049
2078
|
out.push(...computeLcomMetrics(file, nodeId));
|
|
2050
2079
|
return out;
|
|
2051
2080
|
}
|
|
2052
|
-
function symbolComplexityMetrics(fileId2, stats, symbolNames) {
|
|
2053
|
-
if (symbolNames.size === 0) return [];
|
|
2054
|
-
const byName = /* @__PURE__ */ new Map();
|
|
2055
|
-
for (const s of stats) {
|
|
2056
|
-
if (!s.name || !symbolNames.has(s.name)) continue;
|
|
2057
|
-
const prev = byName.get(s.name);
|
|
2058
|
-
if (!prev) byName.set(s.name, { cognitive: s.cognitive, cyclomatic: s.cyclomatic });
|
|
2059
|
-
else {
|
|
2060
|
-
prev.cognitive = Math.max(prev.cognitive, s.cognitive);
|
|
2061
|
-
prev.cyclomatic = Math.max(prev.cyclomatic, s.cyclomatic);
|
|
2062
|
-
}
|
|
2063
|
-
}
|
|
2064
|
-
const out = [];
|
|
2065
|
-
for (const [name, m] of byName) {
|
|
2066
|
-
const sid = symbolId(fileId2, name);
|
|
2067
|
-
out.push({ nodeId: sid, name: "symbol_cognitive", value: m.cognitive, unit: "count" });
|
|
2068
|
-
out.push({ nodeId: sid, name: "symbol_cyclomatic", value: m.cyclomatic, unit: "count" });
|
|
2069
|
-
}
|
|
2070
|
-
return out;
|
|
2071
|
-
}
|
|
2072
2081
|
function countLoc(content) {
|
|
2073
2082
|
return content.split("\n").filter((l) => l.trim() !== "").length;
|
|
2074
2083
|
}
|
|
@@ -2082,7 +2091,8 @@ function analyzeFunctions(file) {
|
|
|
2082
2091
|
name: fn.name === null ? null : qualify(scope, fn.name),
|
|
2083
2092
|
cyclomatic: cyclomaticOf(fn.body, file.language),
|
|
2084
2093
|
cognitive: cognitiveComplexityOf(fn.body, file.language),
|
|
2085
|
-
nestingDepth: nestingDepthOf(fn.body, file.language, 0)
|
|
2094
|
+
nestingDepth: nestingDepthOf(fn.body, file.language, 0),
|
|
2095
|
+
loc: fn.node.endPosition.row - fn.node.startPosition.row + 1
|
|
2086
2096
|
});
|
|
2087
2097
|
});
|
|
2088
2098
|
return stats;
|
|
@@ -2090,12 +2100,12 @@ function analyzeFunctions(file) {
|
|
|
2090
2100
|
function functionAt(node, fnTypes) {
|
|
2091
2101
|
if (fnTypes.has(node.type)) {
|
|
2092
2102
|
const body = node.childForFieldName("body");
|
|
2093
|
-
return body ? { name: node.childForFieldName("name")?.text ?? null, body } : null;
|
|
2103
|
+
return body ? { name: node.childForFieldName("name")?.text ?? null, body, node } : null;
|
|
2094
2104
|
}
|
|
2095
2105
|
if ((node.type === "arrow_function" || node.type === "function_expression") && node.parent?.type === "variable_declarator") {
|
|
2096
2106
|
const body = node.childForFieldName("body");
|
|
2097
2107
|
if (!body) return null;
|
|
2098
|
-
return { name: node.parent.childForFieldName("name")?.text ?? null, body };
|
|
2108
|
+
return { name: node.parent.childForFieldName("name")?.text ?? null, body, node };
|
|
2099
2109
|
}
|
|
2100
2110
|
return null;
|
|
2101
2111
|
}
|
|
@@ -2130,6 +2140,146 @@ function cyclomaticOf(body, language) {
|
|
|
2130
2140
|
return complexity;
|
|
2131
2141
|
}
|
|
2132
2142
|
|
|
2143
|
+
// src/analysis/dead-code-python.ts
|
|
2144
|
+
var TERMINALS = /* @__PURE__ */ new Set([
|
|
2145
|
+
"return_statement",
|
|
2146
|
+
"raise_statement",
|
|
2147
|
+
"break_statement",
|
|
2148
|
+
"continue_statement"
|
|
2149
|
+
]);
|
|
2150
|
+
var SCOPE_TYPES = /* @__PURE__ */ new Set(["function_definition", "lambda", "class_definition"]);
|
|
2151
|
+
var SPLAT_TYPES = /* @__PURE__ */ new Set(["list_splat_pattern", "dictionary_splat_pattern"]);
|
|
2152
|
+
var RECEIVERS = /* @__PURE__ */ new Set(["self", "cls"]);
|
|
2153
|
+
function pythonDeadCode(root) {
|
|
2154
|
+
const out = { unreachable: 0, locals: 0, params: 0 };
|
|
2155
|
+
const visit = (node) => {
|
|
2156
|
+
if (node.type === "block") out.unreachable += unreachableInBlock(node);
|
|
2157
|
+
if (node.type === "function_definition") {
|
|
2158
|
+
const r = analyzeFunction(node);
|
|
2159
|
+
out.locals += r.locals;
|
|
2160
|
+
out.params += r.params;
|
|
2161
|
+
}
|
|
2162
|
+
for (const child of node.namedChildren) {
|
|
2163
|
+
if (child) visit(child);
|
|
2164
|
+
}
|
|
2165
|
+
};
|
|
2166
|
+
visit(root);
|
|
2167
|
+
return out;
|
|
2168
|
+
}
|
|
2169
|
+
function unreachableInBlock(block) {
|
|
2170
|
+
let seenTerminal = false;
|
|
2171
|
+
let count = 0;
|
|
2172
|
+
for (const child of block.namedChildren) {
|
|
2173
|
+
if (!child) continue;
|
|
2174
|
+
if (seenTerminal && child.type !== "comment") count++;
|
|
2175
|
+
if (TERMINALS.has(child.type)) seenTerminal = true;
|
|
2176
|
+
}
|
|
2177
|
+
return count;
|
|
2178
|
+
}
|
|
2179
|
+
function analyzeFunction(fn) {
|
|
2180
|
+
const own = ownBindings(fn);
|
|
2181
|
+
const nested = nestedBindings(fn);
|
|
2182
|
+
const refs = countReferences(fn, own.declIds);
|
|
2183
|
+
const isUnused = (name) => !name.startsWith("_") && !nested.has(name) && !own.declared.has(name) && (refs.get(name) ?? 0) === 0;
|
|
2184
|
+
let params = 0;
|
|
2185
|
+
const paramNames = ownParamNames(fn);
|
|
2186
|
+
for (let i = isStub(fn) ? -1 : paramNames.length - 1; i >= 0; i--) {
|
|
2187
|
+
const name = paramNames[i] ?? null;
|
|
2188
|
+
if (name !== null && isUnused(name)) params++;
|
|
2189
|
+
else break;
|
|
2190
|
+
}
|
|
2191
|
+
const paramSet = new Set(paramNames);
|
|
2192
|
+
let locals = 0;
|
|
2193
|
+
for (const name of own.locals) {
|
|
2194
|
+
if (!paramSet.has(name) && isUnused(name)) locals++;
|
|
2195
|
+
}
|
|
2196
|
+
return { locals, params };
|
|
2197
|
+
}
|
|
2198
|
+
function isStub(fn) {
|
|
2199
|
+
const body = fn.childForFieldName("body");
|
|
2200
|
+
return (body?.namedChildren ?? []).every((stmt) => {
|
|
2201
|
+
if (!stmt || stmt.type === "comment" || stmt.type === "pass_statement") return true;
|
|
2202
|
+
if (stmt.type === "raise_statement") return /^raise NotImplementedError\b/.test(stmt.text);
|
|
2203
|
+
const expr = stmt.type === "expression_statement" ? stmt.namedChild(0) : null;
|
|
2204
|
+
return expr?.type === "ellipsis" || expr?.type === "string";
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
function ownParamNames(fn) {
|
|
2208
|
+
const params = fn.childForFieldName("parameters");
|
|
2209
|
+
if (!params) return [];
|
|
2210
|
+
const out = [];
|
|
2211
|
+
for (const p of params.namedChildren) {
|
|
2212
|
+
if (!p || p.type === "keyword_separator" || p.type === "positional_separator") continue;
|
|
2213
|
+
const id = paramIdentifier(p);
|
|
2214
|
+
out.push(id && !RECEIVERS.has(id.text) ? id.text : null);
|
|
2215
|
+
}
|
|
2216
|
+
return out;
|
|
2217
|
+
}
|
|
2218
|
+
function paramIdentifier(p) {
|
|
2219
|
+
if (p.type === "identifier") return p;
|
|
2220
|
+
if (SPLAT_TYPES.has(p.type)) return null;
|
|
2221
|
+
const inner = p.childForFieldName("name") ?? p.namedChild(0);
|
|
2222
|
+
return inner?.type === "identifier" ? inner : null;
|
|
2223
|
+
}
|
|
2224
|
+
function assignedIdentifier(node) {
|
|
2225
|
+
if (node.type !== "assignment" || !node.childForFieldName("right")) return null;
|
|
2226
|
+
const left = node.childForFieldName("left");
|
|
2227
|
+
return left?.type === "identifier" ? left : null;
|
|
2228
|
+
}
|
|
2229
|
+
function ownBindings(fn) {
|
|
2230
|
+
const out = { locals: /* @__PURE__ */ new Set(), declared: /* @__PURE__ */ new Set(), declIds: /* @__PURE__ */ new Set() };
|
|
2231
|
+
for (const p of fn.childForFieldName("parameters")?.namedChildren ?? []) {
|
|
2232
|
+
const id = p ? paramIdentifier(p) : null;
|
|
2233
|
+
if (id) out.declIds.add(id.id);
|
|
2234
|
+
}
|
|
2235
|
+
const walk2 = (node) => {
|
|
2236
|
+
if (SCOPE_TYPES.has(node.type)) return;
|
|
2237
|
+
const id = assignedIdentifier(node);
|
|
2238
|
+
if (id) {
|
|
2239
|
+
out.locals.add(id.text);
|
|
2240
|
+
out.declIds.add(id.id);
|
|
2241
|
+
}
|
|
2242
|
+
if (node.type === "global_statement" || node.type === "nonlocal_statement") {
|
|
2243
|
+
for (const n of node.namedChildren) if (n) out.declared.add(n.text);
|
|
2244
|
+
}
|
|
2245
|
+
for (const child of node.namedChildren) if (child) walk2(child);
|
|
2246
|
+
};
|
|
2247
|
+
const body = fn.childForFieldName("body");
|
|
2248
|
+
if (body) walk2(body);
|
|
2249
|
+
return out;
|
|
2250
|
+
}
|
|
2251
|
+
function nestedBindings(fn) {
|
|
2252
|
+
const out = /* @__PURE__ */ new Set();
|
|
2253
|
+
const walk2 = (node, nested) => {
|
|
2254
|
+
const inner = nested || node !== fn && SCOPE_TYPES.has(node.type);
|
|
2255
|
+
if (inner) {
|
|
2256
|
+
const id = assignedIdentifier(node);
|
|
2257
|
+
if (id) out.add(id.text);
|
|
2258
|
+
if (node.type === "parameters" || node.type === "lambda_parameters") {
|
|
2259
|
+
for (const p of node.namedChildren) {
|
|
2260
|
+
const pid = p ? paramIdentifier(p) : null;
|
|
2261
|
+
if (pid) out.add(pid.text);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
for (const child of node.namedChildren) if (child) walk2(child, inner);
|
|
2266
|
+
};
|
|
2267
|
+
walk2(fn, false);
|
|
2268
|
+
return out;
|
|
2269
|
+
}
|
|
2270
|
+
function countReferences(fn, declIds) {
|
|
2271
|
+
const out = /* @__PURE__ */ new Map();
|
|
2272
|
+
const walk2 = (node) => {
|
|
2273
|
+
if (node.type === "identifier" && !declIds.has(node.id)) {
|
|
2274
|
+
out.set(node.text, (out.get(node.text) ?? 0) + 1);
|
|
2275
|
+
}
|
|
2276
|
+
for (const child of node.namedChildren) if (child) walk2(child);
|
|
2277
|
+
};
|
|
2278
|
+
const body = fn.childForFieldName("body");
|
|
2279
|
+
if (body) walk2(body);
|
|
2280
|
+
return out;
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2133
2283
|
// src/analysis/dead-code.ts
|
|
2134
2284
|
var DEAD_CODE_METRIC_NAMES = /* @__PURE__ */ new Set([
|
|
2135
2285
|
"unreachable_statements",
|
|
@@ -2145,7 +2295,7 @@ var FUNCTION_TYPES = /* @__PURE__ */ new Set([
|
|
|
2145
2295
|
"generator_function"
|
|
2146
2296
|
]);
|
|
2147
2297
|
var PARAM_TYPES = /* @__PURE__ */ new Set(["required_parameter", "optional_parameter"]);
|
|
2148
|
-
var
|
|
2298
|
+
var TERMINALS2 = /* @__PURE__ */ new Set([
|
|
2149
2299
|
"return_statement",
|
|
2150
2300
|
"throw_statement",
|
|
2151
2301
|
"break_statement",
|
|
@@ -2154,13 +2304,13 @@ var TERMINALS = /* @__PURE__ */ new Set([
|
|
|
2154
2304
|
function computeDeadCodeMetrics(files, fileIdOf) {
|
|
2155
2305
|
const out = [];
|
|
2156
2306
|
for (const file of files) {
|
|
2157
|
-
|
|
2307
|
+
const counts = deadCodeOf(file);
|
|
2308
|
+
if (!counts) continue;
|
|
2158
2309
|
const id = fileIdOf(file.filePath);
|
|
2159
|
-
const unreachable =
|
|
2310
|
+
const { unreachable, locals, params } = counts;
|
|
2160
2311
|
if (unreachable > 0) {
|
|
2161
2312
|
out.push({ nodeId: id, name: "unreachable_statements", value: unreachable, unit: "count" });
|
|
2162
2313
|
}
|
|
2163
|
-
const { locals, params } = countUnusedBindings(file.tree.rootNode);
|
|
2164
2314
|
if (locals > 0) {
|
|
2165
2315
|
out.push({ nodeId: id, name: "unused_locals", value: locals, unit: "count" });
|
|
2166
2316
|
}
|
|
@@ -2170,10 +2320,16 @@ function computeDeadCodeMetrics(files, fileIdOf) {
|
|
|
2170
2320
|
}
|
|
2171
2321
|
return out;
|
|
2172
2322
|
}
|
|
2323
|
+
function deadCodeOf(file) {
|
|
2324
|
+
const root = file.tree.rootNode;
|
|
2325
|
+
if (file.language === "python") return pythonDeadCode(root);
|
|
2326
|
+
if (file.language !== "typescript" && file.language !== "tsx") return null;
|
|
2327
|
+
return { unreachable: countUnreachable(root), ...countUnusedBindings(root) };
|
|
2328
|
+
}
|
|
2173
2329
|
function countUnreachable(root) {
|
|
2174
2330
|
let count = 0;
|
|
2175
2331
|
const visit = (node) => {
|
|
2176
|
-
if (node.type === "statement_block") count +=
|
|
2332
|
+
if (node.type === "statement_block") count += unreachableInBlock2(node);
|
|
2177
2333
|
for (const child of node.namedChildren) {
|
|
2178
2334
|
if (child) visit(child);
|
|
2179
2335
|
}
|
|
@@ -2181,7 +2337,7 @@ function countUnreachable(root) {
|
|
|
2181
2337
|
visit(root);
|
|
2182
2338
|
return count;
|
|
2183
2339
|
}
|
|
2184
|
-
function
|
|
2340
|
+
function unreachableInBlock2(block) {
|
|
2185
2341
|
let seenTerminal = false;
|
|
2186
2342
|
let count = 0;
|
|
2187
2343
|
for (const child of block.namedChildren) {
|
|
@@ -2189,7 +2345,7 @@ function unreachableInBlock(block) {
|
|
|
2189
2345
|
if (seenTerminal && child.type !== "comment" && child.type !== "function_declaration") {
|
|
2190
2346
|
count++;
|
|
2191
2347
|
}
|
|
2192
|
-
if (
|
|
2348
|
+
if (TERMINALS2.has(child.type)) seenTerminal = true;
|
|
2193
2349
|
}
|
|
2194
2350
|
return count;
|
|
2195
2351
|
}
|
|
@@ -2198,7 +2354,7 @@ function countUnusedBindings(root) {
|
|
|
2198
2354
|
let params = 0;
|
|
2199
2355
|
const visit = (node) => {
|
|
2200
2356
|
if (FUNCTION_TYPES.has(node.type)) {
|
|
2201
|
-
const r =
|
|
2357
|
+
const r = analyzeFunction2(node);
|
|
2202
2358
|
locals += r.locals;
|
|
2203
2359
|
params += r.params;
|
|
2204
2360
|
}
|
|
@@ -2209,14 +2365,14 @@ function countUnusedBindings(root) {
|
|
|
2209
2365
|
visit(root);
|
|
2210
2366
|
return { locals, params };
|
|
2211
2367
|
}
|
|
2212
|
-
function
|
|
2368
|
+
function analyzeFunction2(fn) {
|
|
2213
2369
|
const declIds = /* @__PURE__ */ new Set();
|
|
2214
2370
|
const nameCount = /* @__PURE__ */ new Map();
|
|
2215
2371
|
collectBindingDecls(fn, declIds, nameCount);
|
|
2216
2372
|
const refs = /* @__PURE__ */ new Map();
|
|
2217
|
-
|
|
2373
|
+
countReferences2(fn, declIds, refs);
|
|
2218
2374
|
const isUnused = (name) => !name.startsWith("_") && (nameCount.get(name) ?? 0) <= 1 && (refs.get(name) ?? 0) === 0;
|
|
2219
|
-
const paramNames =
|
|
2375
|
+
const paramNames = ownParamNames2(fn);
|
|
2220
2376
|
let params = 0;
|
|
2221
2377
|
for (let i = paramNames.length - 1; i >= 0; i--) {
|
|
2222
2378
|
const name = paramNames[i] ?? null;
|
|
@@ -2229,7 +2385,7 @@ function analyzeFunction(fn) {
|
|
|
2229
2385
|
}
|
|
2230
2386
|
return { locals, params };
|
|
2231
2387
|
}
|
|
2232
|
-
function
|
|
2388
|
+
function ownParamNames2(fn) {
|
|
2233
2389
|
const params = fn.childForFieldName("parameters");
|
|
2234
2390
|
if (!params) return [];
|
|
2235
2391
|
const out = [];
|
|
@@ -2272,7 +2428,7 @@ function collectBindingDecls(fn, ids, nameCount) {
|
|
|
2272
2428
|
};
|
|
2273
2429
|
walk2(fn);
|
|
2274
2430
|
}
|
|
2275
|
-
function
|
|
2431
|
+
function countReferences2(fn, declIds, out) {
|
|
2276
2432
|
const walk2 = (node) => {
|
|
2277
2433
|
if ((node.type === "identifier" || node.type === "shorthand_property_identifier") && !declIds.has(node.id)) {
|
|
2278
2434
|
out.set(node.text, (out.get(node.text) ?? 0) + 1);
|
|
@@ -2962,7 +3118,7 @@ function qualifiedByName(file) {
|
|
|
2962
3118
|
}
|
|
2963
3119
|
|
|
2964
3120
|
// src/indexer.ts
|
|
2965
|
-
var INDEX_VERSION = "0.
|
|
3121
|
+
var INDEX_VERSION = "0.16.0";
|
|
2966
3122
|
var LANGUAGES = ["typescript", "python"];
|
|
2967
3123
|
function canonicalizePath(p) {
|
|
2968
3124
|
try {
|
|
@@ -3930,6 +4086,26 @@ var SOURCE = [
|
|
|
3930
4086
|
source: "source-metrics",
|
|
3931
4087
|
description: "Cyclomatic complexity of the function a symbol names; the max when several functions share the name."
|
|
3932
4088
|
},
|
|
4089
|
+
{
|
|
4090
|
+
name: "symbol_loc",
|
|
4091
|
+
unit: "lines",
|
|
4092
|
+
appliesTo: SYMBOL,
|
|
4093
|
+
rollup: "max",
|
|
4094
|
+
direction: "higher-worse",
|
|
4095
|
+
absent: "exclude",
|
|
4096
|
+
source: "source-metrics",
|
|
4097
|
+
description: "Lines spanned by the function a symbol names, signature included; the max when several share the name."
|
|
4098
|
+
},
|
|
4099
|
+
{
|
|
4100
|
+
name: "symbol_max_nesting",
|
|
4101
|
+
unit: "count",
|
|
4102
|
+
appliesTo: SYMBOL,
|
|
4103
|
+
rollup: "max",
|
|
4104
|
+
direction: "higher-worse",
|
|
4105
|
+
absent: "exclude",
|
|
4106
|
+
source: "source-metrics",
|
|
4107
|
+
description: "Deepest block nesting inside the function a symbol names; the max when several share the name."
|
|
4108
|
+
},
|
|
3933
4109
|
{
|
|
3934
4110
|
name: "class_count",
|
|
3935
4111
|
unit: "count",
|