145 lines
3.6 KiB
Plaintext
145 lines
3.6 KiB
Plaintext
// analyze.cm — Static analysis over index data.
|
|
//
|
|
// All functions take an index object (from index.cm) and return structured results.
|
|
// Does not depend on streamline — operates purely on source-semantic data.
|
|
|
|
var analyze = {}
|
|
|
|
// Find all references to a name, with optional scope filter.
|
|
// scope: "top" (enclosing == null), "fn" (enclosing != null), null (all)
|
|
analyze.find_refs = function(idx, name, scope) {
|
|
var hits = []
|
|
var i = 0
|
|
var ref = null
|
|
while (i < length(idx.references)) {
|
|
ref = idx.references[i]
|
|
if (ref.name == name) {
|
|
if (scope == null) {
|
|
hits[] = ref
|
|
} else if (scope == "top" && ref.enclosing == null) {
|
|
hits[] = ref
|
|
} else if (scope == "fn" && ref.enclosing != null) {
|
|
hits[] = ref
|
|
}
|
|
}
|
|
i = i + 1
|
|
}
|
|
return hits
|
|
}
|
|
|
|
// Find all <name>.<property> usage patterns (channel analysis).
|
|
// Only counts unshadowed uses (name not declared as local var in scope).
|
|
analyze.channels = function(idx, name) {
|
|
var channels = {}
|
|
var summary = {}
|
|
var i = 0
|
|
var cs = null
|
|
var callee = null
|
|
var prop = null
|
|
var prefix_dot = name + "."
|
|
while (i < length(idx.call_sites)) {
|
|
cs = idx.call_sites[i]
|
|
callee = cs.callee
|
|
if (callee != null && starts_with(callee, prefix_dot)) {
|
|
prop = text(callee, length(prefix_dot), length(callee))
|
|
if (channels[prop] == null) {
|
|
channels[prop] = []
|
|
}
|
|
channels[prop][] = {span: cs.span}
|
|
if (summary[prop] == null) {
|
|
summary[prop] = 0
|
|
}
|
|
summary[prop] = summary[prop] + 1
|
|
}
|
|
i = i + 1
|
|
}
|
|
return {channels: channels, summary: summary}
|
|
}
|
|
|
|
// Find declarations by name, with optional kind filter.
|
|
// kind: "var", "def", "fn", "param", or null (any)
|
|
analyze.find_decls = function(idx, name, kind) {
|
|
var hits = []
|
|
var i = 0
|
|
var sym = null
|
|
while (i < length(idx.symbols)) {
|
|
sym = idx.symbols[i]
|
|
if (sym.name == name) {
|
|
if (kind == null || sym.kind == kind) {
|
|
hits[] = sym
|
|
}
|
|
}
|
|
i = i + 1
|
|
}
|
|
return hits
|
|
}
|
|
|
|
// Find intrinsic usage by name.
|
|
analyze.find_intrinsic = function(idx, name) {
|
|
var hits = []
|
|
var i = 0
|
|
var ref = null
|
|
if (idx.intrinsic_refs == null) return hits
|
|
while (i < length(idx.intrinsic_refs)) {
|
|
ref = idx.intrinsic_refs[i]
|
|
if (ref.name == name) {
|
|
hits[] = ref
|
|
}
|
|
i = i + 1
|
|
}
|
|
return hits
|
|
}
|
|
|
|
// Call sites with >4 args — always a compile error (max arity is 4).
|
|
analyze.excess_args = function(idx) {
|
|
var hits = []
|
|
var i = 0
|
|
var cs = null
|
|
while (i < length(idx.call_sites)) {
|
|
cs = idx.call_sites[i]
|
|
if (cs.args_count > 4) {
|
|
hits[] = {span: cs.span, callee: cs.callee, args_count: cs.args_count}
|
|
}
|
|
i = i + 1
|
|
}
|
|
return hits
|
|
}
|
|
|
|
// Extract module export shape from index data (for cross-module analysis).
|
|
analyze.module_summary = function(idx) {
|
|
var exports = {}
|
|
var i = 0
|
|
var j = 0
|
|
var exp = null
|
|
var sym = null
|
|
var found = false
|
|
if (idx.exports == null) return {exports: exports}
|
|
while (i < length(idx.exports)) {
|
|
exp = idx.exports[i]
|
|
found = false
|
|
if (exp.symbol_id != null) {
|
|
j = 0
|
|
while (j < length(idx.symbols)) {
|
|
sym = idx.symbols[j]
|
|
if (sym.symbol_id == exp.symbol_id) {
|
|
if (sym.kind == "fn" && sym.params != null) {
|
|
exports[exp.name] = {type: "function", arity: length(sym.params)}
|
|
} else {
|
|
exports[exp.name] = {type: sym.kind}
|
|
}
|
|
found = true
|
|
break
|
|
}
|
|
j = j + 1
|
|
}
|
|
}
|
|
if (!found) {
|
|
exports[exp.name] = {type: "unknown"}
|
|
}
|
|
i = i + 1
|
|
}
|
|
return {exports: exports}
|
|
}
|
|
|
|
return analyze
|