sf-agentpmd 0.1.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/LICENSE +21 -0
- package/NOTICE +26 -0
- package/README.md +204 -0
- package/bin/dev.js +5 -0
- package/bin/run.js +3 -0
- package/dist/analyzer/action-references.d.ts +21 -0
- package/dist/analyzer/action-references.js +130 -0
- package/dist/analyzer/action-references.js.map +1 -0
- package/dist/analyzer/analyze.d.ts +43 -0
- package/dist/analyzer/analyze.js +222 -0
- package/dist/analyzer/analyze.js.map +1 -0
- package/dist/analyzer/apex-analyze.d.ts +14 -0
- package/dist/analyzer/apex-analyze.js +60 -0
- package/dist/analyzer/apex-analyze.js.map +1 -0
- package/dist/analyzer/apex-complexity.d.ts +27 -0
- package/dist/analyzer/apex-complexity.js +133 -0
- package/dist/analyzer/apex-complexity.js.map +1 -0
- package/dist/analyzer/apex-parse.d.ts +39 -0
- package/dist/analyzer/apex-parse.js +32 -0
- package/dist/analyzer/apex-parse.js.map +1 -0
- package/dist/analyzer/apex-resolve.d.ts +32 -0
- package/dist/analyzer/apex-resolve.js +59 -0
- package/dist/analyzer/apex-resolve.js.map +1 -0
- package/dist/analyzer/complexity.d.ts +30 -0
- package/dist/analyzer/complexity.js +126 -0
- package/dist/analyzer/complexity.js.map +1 -0
- package/dist/analyzer/parse.d.ts +51 -0
- package/dist/analyzer/parse.js +143 -0
- package/dist/analyzer/parse.js.map +1 -0
- package/dist/analyzer/project.d.ts +12 -0
- package/dist/analyzer/project.js +51 -0
- package/dist/analyzer/project.js.map +1 -0
- package/dist/analyzer/types.d.ts +76 -0
- package/dist/analyzer/types.js +2 -0
- package/dist/analyzer/types.js.map +1 -0
- package/dist/commands/agentpmd/analyze.d.ts +20 -0
- package/dist/commands/agentpmd/analyze.js +122 -0
- package/dist/commands/agentpmd/analyze.js.map +1 -0
- package/dist/commands/agentpmd/install-skill.d.ts +11 -0
- package/dist/commands/agentpmd/install-skill.js +33 -0
- package/dist/commands/agentpmd/install-skill.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/renderers/csv.d.ts +6 -0
- package/dist/renderers/csv.js +78 -0
- package/dist/renderers/csv.js.map +1 -0
- package/dist/renderers/index.d.ts +8 -0
- package/dist/renderers/index.js +25 -0
- package/dist/renderers/index.js.map +1 -0
- package/dist/renderers/markdown.d.ts +12 -0
- package/dist/renderers/markdown.js +233 -0
- package/dist/renderers/markdown.js.map +1 -0
- package/dist/renderers/options.d.ts +20 -0
- package/dist/renderers/options.js +2 -0
- package/dist/renderers/options.js.map +1 -0
- package/dist/renderers/sarif.d.ts +3 -0
- package/dist/renderers/sarif.js +131 -0
- package/dist/renderers/sarif.js.map +1 -0
- package/dist/renderers/text.d.ts +3 -0
- package/dist/renderers/text.js +243 -0
- package/dist/renderers/text.js.map +1 -0
- package/oclif.manifest.json +168 -0
- package/package.json +97 -0
- package/skill/SKILL.md +103 -0
- package/skill/references/command-structure.md +89 -0
- package/skill/references/install.md +112 -0
- package/skill/references/output-formats.md +205 -0
- package/skill/references/upgrade.md +112 -0
- package/vendor/agentscript-parser-javascript/dist/cst-node.d.ts +83 -0
- package/vendor/agentscript-parser-javascript/dist/cst-node.js +238 -0
- package/vendor/agentscript-parser-javascript/dist/errors.d.ts +34 -0
- package/vendor/agentscript-parser-javascript/dist/errors.js +74 -0
- package/vendor/agentscript-parser-javascript/dist/expressions.d.ts +36 -0
- package/vendor/agentscript-parser-javascript/dist/expressions.js +682 -0
- package/vendor/agentscript-parser-javascript/dist/highlighter.d.ts +24 -0
- package/vendor/agentscript-parser-javascript/dist/highlighter.js +260 -0
- package/vendor/agentscript-parser-javascript/dist/index.d.ts +29 -0
- package/vendor/agentscript-parser-javascript/dist/index.js +35 -0
- package/vendor/agentscript-parser-javascript/dist/lexer.d.ts +60 -0
- package/vendor/agentscript-parser-javascript/dist/lexer.js +630 -0
- package/vendor/agentscript-parser-javascript/dist/parse-mapping.d.ts +46 -0
- package/vendor/agentscript-parser-javascript/dist/parse-mapping.js +549 -0
- package/vendor/agentscript-parser-javascript/dist/parse-sequence.d.ts +10 -0
- package/vendor/agentscript-parser-javascript/dist/parse-sequence.js +118 -0
- package/vendor/agentscript-parser-javascript/dist/parse-statements.d.ts +15 -0
- package/vendor/agentscript-parser-javascript/dist/parse-statements.js +519 -0
- package/vendor/agentscript-parser-javascript/dist/parse-templates.d.ts +15 -0
- package/vendor/agentscript-parser-javascript/dist/parse-templates.js +323 -0
- package/vendor/agentscript-parser-javascript/dist/parser.d.ts +65 -0
- package/vendor/agentscript-parser-javascript/dist/parser.js +163 -0
- package/vendor/agentscript-parser-javascript/dist/recovery.d.ts +51 -0
- package/vendor/agentscript-parser-javascript/dist/recovery.js +199 -0
- package/vendor/agentscript-parser-javascript/dist/token.d.ts +58 -0
- package/vendor/agentscript-parser-javascript/dist/token.js +62 -0
- package/vendor/agentscript-parser-javascript/package.json +19 -0
- package/vendor/agentscript-types/dist/comment.d.ts +11 -0
- package/vendor/agentscript-types/dist/comment.js +10 -0
- package/vendor/agentscript-types/dist/cst.d.ts +7 -0
- package/vendor/agentscript-types/dist/cst.js +8 -0
- package/vendor/agentscript-types/dist/diagnostic.d.ts +34 -0
- package/vendor/agentscript-types/dist/diagnostic.js +23 -0
- package/vendor/agentscript-types/dist/index.d.ts +9 -0
- package/vendor/agentscript-types/dist/index.js +10 -0
- package/vendor/agentscript-types/dist/position.d.ts +11 -0
- package/vendor/agentscript-types/dist/position.js +16 -0
- package/vendor/agentscript-types/dist/syntax-node.d.ts +39 -0
- package/vendor/agentscript-types/dist/syntax-node.js +8 -0
- package/vendor/agentscript-types/package.json +15 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { readdir, readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
3
|
+
import { collectDeclarations, collectReferences } from './action-references.js';
|
|
4
|
+
import { analyzeReferencedApex } from './apex-analyze.js';
|
|
5
|
+
import { collectScopes, complexityForFile } from './complexity.js';
|
|
6
|
+
import { extractDeveloperName, parseAgentSource } from './parse.js';
|
|
7
|
+
/**
|
|
8
|
+
* Error thrown when --api-name filters produce no matches. Carries the
|
|
9
|
+
* candidate list so callers can show a useful hint.
|
|
10
|
+
*/
|
|
11
|
+
export class NoMatchingBundlesError extends Error {
|
|
12
|
+
requested;
|
|
13
|
+
available;
|
|
14
|
+
constructor(requested, available) {
|
|
15
|
+
super(`No agent bundle matched ${requested.map(n => `'${n}'`).join(', ')}. ` +
|
|
16
|
+
`Available: ${available.map((b) => formatBundleIdentity(b)).join(', ')}`);
|
|
17
|
+
this.requested = requested;
|
|
18
|
+
this.available = available;
|
|
19
|
+
this.name = 'NoMatchingBundlesError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function formatBundleIdentity(b) {
|
|
23
|
+
if (b.developerName && b.developerName !== b.dirName) {
|
|
24
|
+
return `${b.developerName} (dir: ${b.dirName})`;
|
|
25
|
+
}
|
|
26
|
+
return b.dirName;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Analyze AgentScript bundles under one or more source roots.
|
|
30
|
+
*
|
|
31
|
+
* Backward-compatible: pass a single path (string) for the legacy
|
|
32
|
+
* single-root case. Pass an array of paths for multi-root analysis
|
|
33
|
+
* (e.g. multiple `packageDirectories` in an sfdx project).
|
|
34
|
+
*/
|
|
35
|
+
export async function analyzeSource(rootPathOrPaths, options = {}) {
|
|
36
|
+
const rawRoots = Array.isArray(rootPathOrPaths)
|
|
37
|
+
? rootPathOrPaths
|
|
38
|
+
: [rootPathOrPaths];
|
|
39
|
+
if (rawRoots.length === 0) {
|
|
40
|
+
throw new Error('analyzeSource requires at least one source path');
|
|
41
|
+
}
|
|
42
|
+
const absRoots = rawRoots.map(p => resolve(p));
|
|
43
|
+
const reportBase = options.reportBase
|
|
44
|
+
? resolve(options.reportBase)
|
|
45
|
+
: absRoots.length === 1
|
|
46
|
+
? absRoots[0]
|
|
47
|
+
: longestCommonAncestor(absRoots);
|
|
48
|
+
const allFiles = [];
|
|
49
|
+
for (const root of absRoots) {
|
|
50
|
+
const files = await findAgentFiles(root);
|
|
51
|
+
for (const f of files)
|
|
52
|
+
if (!allFiles.includes(f))
|
|
53
|
+
allFiles.push(f);
|
|
54
|
+
}
|
|
55
|
+
allFiles.sort();
|
|
56
|
+
const filteredFiles = await filterByApiNames(allFiles, options.apiNames);
|
|
57
|
+
const fileReports = [];
|
|
58
|
+
for (const file of filteredFiles) {
|
|
59
|
+
fileReports.push(await analyzeFile(file, reportBase));
|
|
60
|
+
}
|
|
61
|
+
const apex = await analyzeReferencedApex({
|
|
62
|
+
agentAbsPaths: filteredFiles,
|
|
63
|
+
apexSourceOverride: options.apexSourceOverride,
|
|
64
|
+
fileReports,
|
|
65
|
+
sourceDirRoot: reportBase,
|
|
66
|
+
});
|
|
67
|
+
const report = {
|
|
68
|
+
apexClasses: apex.classes,
|
|
69
|
+
byTargetKind: tallyTargets(fileReports),
|
|
70
|
+
files: fileReports,
|
|
71
|
+
totalApexComplexity: apex.classes.reduce((acc, c) => acc + c.classComplexity, 0),
|
|
72
|
+
totalComplexity: fileReports.reduce((acc, f) => acc + f.fileComplexity, 0),
|
|
73
|
+
totalDeclarations: fileReports.reduce((acc, f) => acc + f.declarations.length, 0),
|
|
74
|
+
totalReferences: fileReports.reduce((acc, f) => acc + f.references.length, 0),
|
|
75
|
+
unresolvedApexTargets: apex.unresolved,
|
|
76
|
+
};
|
|
77
|
+
return report;
|
|
78
|
+
}
|
|
79
|
+
export async function analyzeFile(absPath, base) {
|
|
80
|
+
const source = await readFile(absPath, 'utf8');
|
|
81
|
+
const root = parseAgentSource(source);
|
|
82
|
+
const cc = complexityForFile(root);
|
|
83
|
+
const { procedures } = cc;
|
|
84
|
+
const scopes = collectScopes(root);
|
|
85
|
+
const declarations = [];
|
|
86
|
+
const references = [];
|
|
87
|
+
for (const s of scopes) {
|
|
88
|
+
declarations.push(...collectDeclarations(s));
|
|
89
|
+
references.push(...collectReferences(s));
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
declarations,
|
|
93
|
+
fileComplexity: cc.total,
|
|
94
|
+
parseErrors: [], // CST is error-tolerant; surface diagnostics in a later iteration.
|
|
95
|
+
path: relative(base, absPath),
|
|
96
|
+
procedures,
|
|
97
|
+
references,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async function findAgentFiles(root) {
|
|
101
|
+
const s = await stat(root).catch(() => { });
|
|
102
|
+
if (!s)
|
|
103
|
+
throw new Error(`source path does not exist: ${root}`);
|
|
104
|
+
if (s.isFile())
|
|
105
|
+
return root.endsWith('.agent') ? [root] : [];
|
|
106
|
+
const out = [];
|
|
107
|
+
const visit = async (dir) => {
|
|
108
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
109
|
+
for (const e of entries) {
|
|
110
|
+
if (e.name.startsWith('.'))
|
|
111
|
+
continue;
|
|
112
|
+
if (e.name === 'node_modules' || e.name === 'vendor' || e.name === 'lib')
|
|
113
|
+
continue;
|
|
114
|
+
const full = join(dir, e.name);
|
|
115
|
+
if (e.isDirectory())
|
|
116
|
+
await visit(full);
|
|
117
|
+
else if (e.isFile() && e.name.endsWith('.agent'))
|
|
118
|
+
out.push(full);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
await visit(root);
|
|
122
|
+
out.sort();
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
function tallyTargets(files) {
|
|
126
|
+
const acc = {
|
|
127
|
+
apex: 0,
|
|
128
|
+
flow: 0,
|
|
129
|
+
prompt: 0,
|
|
130
|
+
unknown: 0,
|
|
131
|
+
utils: 0,
|
|
132
|
+
};
|
|
133
|
+
for (const f of files)
|
|
134
|
+
for (const d of f.declarations)
|
|
135
|
+
acc[d.targetKind]++;
|
|
136
|
+
return acc;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Filter the discovered .agent file list by the supplied `--api-name`
|
|
140
|
+
* values. A bundle matches if (a) its directory name equals a requested
|
|
141
|
+
* name, or (b) its `config.developer_name` equals one. Bundles whose dir
|
|
142
|
+
* name already matches are kept without parsing; only the remainder are
|
|
143
|
+
* parsed for `developer_name` resolution.
|
|
144
|
+
*
|
|
145
|
+
* Throws NoMatchingBundlesError when filters were supplied but nothing
|
|
146
|
+
* matched, so callers can show the available list.
|
|
147
|
+
*/
|
|
148
|
+
async function filterByApiNames(files, apiNames) {
|
|
149
|
+
if (!apiNames || apiNames.length === 0)
|
|
150
|
+
return files;
|
|
151
|
+
const wanted = new Set(apiNames);
|
|
152
|
+
const matches = [];
|
|
153
|
+
const unmatched = [];
|
|
154
|
+
for (const file of files) {
|
|
155
|
+
const dirName = basename(dirname(file));
|
|
156
|
+
if (wanted.has(dirName)) {
|
|
157
|
+
matches.push(file);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
unmatched.push(file);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (matches.length === files.length || unmatched.length === 0) {
|
|
164
|
+
return matches;
|
|
165
|
+
}
|
|
166
|
+
// Slow path: parse the still-unmatched files to read developer_name.
|
|
167
|
+
const identities = [];
|
|
168
|
+
for (const file of unmatched) {
|
|
169
|
+
const dirName = basename(dirname(file));
|
|
170
|
+
const developerName = await readDeveloperName(file);
|
|
171
|
+
identities.push({ developerName, dirName, path: file });
|
|
172
|
+
if (developerName && wanted.has(developerName)) {
|
|
173
|
+
matches.push(file);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (matches.length === 0) {
|
|
177
|
+
// Build the full candidate list (matched dirs + parsed unmatched) for
|
|
178
|
+
// the error message.
|
|
179
|
+
const all = [];
|
|
180
|
+
for (const file of files) {
|
|
181
|
+
const existing = identities.find(i => i.path === file);
|
|
182
|
+
if (existing) {
|
|
183
|
+
all.push(existing);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
all.push({
|
|
187
|
+
developerName: await readDeveloperName(file),
|
|
188
|
+
dirName: basename(dirname(file)),
|
|
189
|
+
path: file,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
all.sort((a, b) => a.dirName.localeCompare(b.dirName));
|
|
194
|
+
throw new NoMatchingBundlesError(apiNames, all);
|
|
195
|
+
}
|
|
196
|
+
matches.sort();
|
|
197
|
+
return matches;
|
|
198
|
+
}
|
|
199
|
+
async function readDeveloperName(file) {
|
|
200
|
+
const source = await readFile(file, 'utf8');
|
|
201
|
+
const root = parseAgentSource(source);
|
|
202
|
+
return extractDeveloperName(root);
|
|
203
|
+
}
|
|
204
|
+
function longestCommonAncestor(paths) {
|
|
205
|
+
if (paths.length === 0)
|
|
206
|
+
return process.cwd();
|
|
207
|
+
if (paths.length === 1)
|
|
208
|
+
return paths[0];
|
|
209
|
+
const split = paths.map(p => p.split('/'));
|
|
210
|
+
const minLen = Math.min(...split.map(s => s.length));
|
|
211
|
+
const common = [];
|
|
212
|
+
for (let i = 0; i < minLen; i++) {
|
|
213
|
+
const seg = split[0][i];
|
|
214
|
+
if (split.every(s => s[i] === seg))
|
|
215
|
+
common.push(seg);
|
|
216
|
+
else
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
const joined = common.join('/') || '/';
|
|
220
|
+
return joined;
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../src/analyzer/analyze.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ApexClassReport, FileReport } from './types.js';
|
|
2
|
+
/** Inputs needed to resolve and analyze every apex:// target in the bundle. */
|
|
3
|
+
export interface ApexAnalyzeInputs {
|
|
4
|
+
/** Absolute paths of every .agent file analyzed, parallel to fileReports. */
|
|
5
|
+
agentAbsPaths: string[];
|
|
6
|
+
apexSourceOverride?: string;
|
|
7
|
+
fileReports: FileReport[];
|
|
8
|
+
sourceDirRoot: string;
|
|
9
|
+
}
|
|
10
|
+
export interface ApexAnalyzeOutputs {
|
|
11
|
+
classes: ApexClassReport[];
|
|
12
|
+
unresolved: string[];
|
|
13
|
+
}
|
|
14
|
+
export declare function analyzeReferencedApex(inputs: ApexAnalyzeInputs): Promise<ApexAnalyzeOutputs>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { basename, relative } from 'node:path';
|
|
3
|
+
import { complexityOfMethod, methodsInCompilationUnit } from './apex-complexity.js';
|
|
4
|
+
import { parseApexSource } from './apex-parse.js';
|
|
5
|
+
import { extractApexClassName, resolveApexClassPath, } from './apex-resolve.js';
|
|
6
|
+
export async function analyzeReferencedApex(inputs) {
|
|
7
|
+
const byPath = new Map();
|
|
8
|
+
const unresolved = new Set();
|
|
9
|
+
for (let i = 0; i < inputs.fileReports.length; i++) {
|
|
10
|
+
const fr = inputs.fileReports[i];
|
|
11
|
+
const agentAbs = inputs.agentAbsPaths[i];
|
|
12
|
+
for (const decl of fr.declarations) {
|
|
13
|
+
if (decl.targetKind !== 'apex' || !decl.target)
|
|
14
|
+
continue;
|
|
15
|
+
const className = extractApexClassName(decl.target);
|
|
16
|
+
if (!className) {
|
|
17
|
+
unresolved.add(decl.target);
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
const path = await resolveApexClassPath(className, {
|
|
21
|
+
agentFilePath: agentAbs,
|
|
22
|
+
apexSourceOverride: inputs.apexSourceOverride,
|
|
23
|
+
sourceDirRoot: inputs.sourceDirRoot,
|
|
24
|
+
});
|
|
25
|
+
if (!path) {
|
|
26
|
+
unresolved.add(decl.target);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const existing = byPath.get(path);
|
|
30
|
+
if (existing) {
|
|
31
|
+
if (!existing.referencedBy.includes(fr.path)) {
|
|
32
|
+
existing.referencedBy.push(fr.path);
|
|
33
|
+
}
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
const report = await analyzeApexClass(path, className, [fr.path], inputs.sourceDirRoot);
|
|
37
|
+
byPath.set(path, report);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
classes: [...byPath.values()].sort((a, b) => a.className.localeCompare(b.className)),
|
|
42
|
+
unresolved: [...unresolved].sort(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function analyzeApexClass(absPath, className, referencedBy, sourceDirRoot) {
|
|
46
|
+
const source = await readFile(absPath, 'utf8');
|
|
47
|
+
const cu = parseApexSource(source);
|
|
48
|
+
const methodCtxs = methodsInCompilationUnit(cu);
|
|
49
|
+
const methods = methodCtxs.map((m) => complexityOfMethod(m));
|
|
50
|
+
const classComplexity = methods.reduce((acc, m) => acc + m.complexity, 0);
|
|
51
|
+
return {
|
|
52
|
+
classComplexity,
|
|
53
|
+
className: className || basename(absPath, '.cls'),
|
|
54
|
+
methods,
|
|
55
|
+
parseErrors: [],
|
|
56
|
+
path: relative(sourceDirRoot, absPath),
|
|
57
|
+
referencedBy,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=apex-analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apex-analyze.js","sourceRoot":"","sources":["../../src/analyzer/apex-analyze.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type CompilationUnitContext, ConstructorDeclarationContext, MethodDeclarationContext } from '@apexdevtools/apex-parser';
|
|
2
|
+
import type { ApexMethodCC } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Standard McCabe Cyclomatic Complexity for Apex, per the SonarQube /
|
|
5
|
+
* PMD CyclomaticComplexity convention used in the categorization
|
|
6
|
+
* whitepaper § 7:
|
|
7
|
+
*
|
|
8
|
+
* CC = 1
|
|
9
|
+
* + count(if)
|
|
10
|
+
* + count(for) // includes enhanced-for (for-each)
|
|
11
|
+
* + count(while)
|
|
12
|
+
* + count(do-while)
|
|
13
|
+
* + count(when arm) // each switch `when X { ... }`; `when else` excluded
|
|
14
|
+
* + count(catch) // each catch clause
|
|
15
|
+
* + count(ternary ?:)
|
|
16
|
+
* + count(&&)
|
|
17
|
+
* + count(||)
|
|
18
|
+
*
|
|
19
|
+
* Not counted: else, when else, finally, try itself.
|
|
20
|
+
*/
|
|
21
|
+
export declare function complexityOfMethod(methodCtx: ConstructorDeclarationContext | MethodDeclarationContext): ApexMethodCC;
|
|
22
|
+
/**
|
|
23
|
+
* Enumerate every method and constructor in a compilation unit, including
|
|
24
|
+
* those nested inside inner classes. Each gets its own CC entry per the
|
|
25
|
+
* v2 scope ("all methods in any class touched by apex://").
|
|
26
|
+
*/
|
|
27
|
+
export declare function methodsInCompilationUnit(cu: CompilationUnitContext): Array<ConstructorDeclarationContext | MethodDeclarationContext>;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { CatchClauseContext, CondExpressionContext, ConstructorDeclarationContext, DoWhileStatementContext, ForStatementContext, IfStatementContext, LogAndExpressionContext, LogOrExpressionContext, MethodDeclarationContext, WhenControlContext, WhileStatementContext, } from '@apexdevtools/apex-parser';
|
|
2
|
+
import { locOfCtx, walkParseTree } from './apex-parse.js';
|
|
3
|
+
/**
|
|
4
|
+
* Standard McCabe Cyclomatic Complexity for Apex, per the SonarQube /
|
|
5
|
+
* PMD CyclomaticComplexity convention used in the categorization
|
|
6
|
+
* whitepaper § 7:
|
|
7
|
+
*
|
|
8
|
+
* CC = 1
|
|
9
|
+
* + count(if)
|
|
10
|
+
* + count(for) // includes enhanced-for (for-each)
|
|
11
|
+
* + count(while)
|
|
12
|
+
* + count(do-while)
|
|
13
|
+
* + count(when arm) // each switch `when X { ... }`; `when else` excluded
|
|
14
|
+
* + count(catch) // each catch clause
|
|
15
|
+
* + count(ternary ?:)
|
|
16
|
+
* + count(&&)
|
|
17
|
+
* + count(||)
|
|
18
|
+
*
|
|
19
|
+
* Not counted: else, when else, finally, try itself.
|
|
20
|
+
*/
|
|
21
|
+
export function complexityOfMethod(methodCtx) {
|
|
22
|
+
const body = methodCtx.block();
|
|
23
|
+
const contributors = [];
|
|
24
|
+
if (body) {
|
|
25
|
+
walkParseTree(body, node => {
|
|
26
|
+
// Don't descend into nested methods/constructors — they have their
|
|
27
|
+
// own CC counted separately.
|
|
28
|
+
if (node !== methodCtx &&
|
|
29
|
+
(node instanceof MethodDeclarationContext ||
|
|
30
|
+
node instanceof ConstructorDeclarationContext)) {
|
|
31
|
+
// walkParseTree will still recurse into this node's subtree.
|
|
32
|
+
// We can't easily prune, so we filter by parentage: skip
|
|
33
|
+
// contributors whose nearest enclosing method/ctor is not us.
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const kind = classifyContributor(node);
|
|
37
|
+
if (kind !== undefined && enclosingMethod(node) === methodCtx) {
|
|
38
|
+
contributors.push({ kind, location: locOfCtx(node) });
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const name = methodCtx instanceof ConstructorDeclarationContext
|
|
43
|
+
? methodCtx.qualifiedName()?.getText() ?? '<ctor>'
|
|
44
|
+
: methodCtx.id()?.getText() ?? '<anon>';
|
|
45
|
+
const signature = renderSignature(methodCtx, name);
|
|
46
|
+
return {
|
|
47
|
+
complexity: 1 + contributors.length,
|
|
48
|
+
contributors,
|
|
49
|
+
kind: methodCtx instanceof ConstructorDeclarationContext ? 'constructor' : 'method',
|
|
50
|
+
location: locOfCtx(methodCtx),
|
|
51
|
+
name,
|
|
52
|
+
signature,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function classifyContributor(node) {
|
|
56
|
+
if (node instanceof IfStatementContext)
|
|
57
|
+
return 'if_statement';
|
|
58
|
+
if (node instanceof ForStatementContext)
|
|
59
|
+
return 'for_statement';
|
|
60
|
+
if (node instanceof WhileStatementContext)
|
|
61
|
+
return 'while_statement';
|
|
62
|
+
if (node instanceof DoWhileStatementContext)
|
|
63
|
+
return 'do_while_statement';
|
|
64
|
+
if (node instanceof WhenControlContext) {
|
|
65
|
+
// `when else { ... }` is the default arm → don't count it.
|
|
66
|
+
const wv = node.whenValue();
|
|
67
|
+
const elseTok = wv && typeof wv.ELSE === 'function' ? wv.ELSE() : undefined;
|
|
68
|
+
return elseTok ? undefined : 'when_arm';
|
|
69
|
+
}
|
|
70
|
+
if (node instanceof CatchClauseContext)
|
|
71
|
+
return 'catch_clause';
|
|
72
|
+
if (node instanceof CondExpressionContext)
|
|
73
|
+
return 'ternary';
|
|
74
|
+
if (node instanceof LogAndExpressionContext)
|
|
75
|
+
return 'short_circuit_and';
|
|
76
|
+
if (node instanceof LogOrExpressionContext)
|
|
77
|
+
return 'short_circuit_or';
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
function enclosingMethod(node) {
|
|
81
|
+
let p = node.parentCtx;
|
|
82
|
+
while (p) {
|
|
83
|
+
if (p instanceof MethodDeclarationContext || p instanceof ConstructorDeclarationContext) {
|
|
84
|
+
return p;
|
|
85
|
+
}
|
|
86
|
+
p = p.parentCtx;
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
function renderSignature(ctx, name) {
|
|
91
|
+
const paramsText = renderFormalParameters(ctx.formalParameters());
|
|
92
|
+
if (ctx instanceof MethodDeclarationContext) {
|
|
93
|
+
const retType = ctx.typeRef()?.getText() ?? (ctx.VOID() ? 'void' : '');
|
|
94
|
+
return `${retType ? retType + ' ' : ''}${name}${paramsText}`;
|
|
95
|
+
}
|
|
96
|
+
return `${name}${paramsText}`;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* ANTLR's `getText()` concatenates child tokens with no whitespace, which
|
|
100
|
+
* mangles parameter lists like `List<Request> requests` into
|
|
101
|
+
* `List<Request>requests`. Walk the structured children and rebuild a
|
|
102
|
+
* properly-spaced signature.
|
|
103
|
+
*/
|
|
104
|
+
function renderFormalParameters(params) {
|
|
105
|
+
if (!params)
|
|
106
|
+
return '()';
|
|
107
|
+
const list = params.formalParameterList();
|
|
108
|
+
if (!list)
|
|
109
|
+
return '()';
|
|
110
|
+
const parts = list.formalParameter_list().map(p => {
|
|
111
|
+
const type = p.typeRef()?.getText() ?? '';
|
|
112
|
+
const id = p.id()?.getText() ?? '';
|
|
113
|
+
return type && id ? `${type} ${id}` : type || id;
|
|
114
|
+
});
|
|
115
|
+
return `(${parts.join(', ')})`;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Enumerate every method and constructor in a compilation unit, including
|
|
119
|
+
* those nested inside inner classes. Each gets its own CC entry per the
|
|
120
|
+
* v2 scope ("all methods in any class touched by apex://").
|
|
121
|
+
*/
|
|
122
|
+
export function methodsInCompilationUnit(cu) {
|
|
123
|
+
const out = [];
|
|
124
|
+
walkParseTree(cu, node => {
|
|
125
|
+
if ((node instanceof MethodDeclarationContext ||
|
|
126
|
+
node instanceof ConstructorDeclarationContext) &&
|
|
127
|
+
node.block()) {
|
|
128
|
+
out.push(node);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=apex-complexity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apex-complexity.js","sourceRoot":"","sources":["../../src/analyzer/apex-complexity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CompilationUnitContext } from '@apexdevtools/apex-parser';
|
|
2
|
+
/**
|
|
3
|
+
* Structural view of an ANTLR parse-tree node covering only the members this
|
|
4
|
+
* analyzer touches. `@apexdevtools/apex-parser` 5.x ships generated context
|
|
5
|
+
* types that extend antlr4's `ParserRuleContext`, but antlr4 4.13.x does not
|
|
6
|
+
* surface `ParserRuleContext` (nor its inherited `getText`/`getChild`) through
|
|
7
|
+
* its NodeNext `.d.cts` re-export chain, so importing it directly fails to
|
|
8
|
+
* resolve. We model the contract locally instead — the runtime objects carry
|
|
9
|
+
* all of these methods.
|
|
10
|
+
*/
|
|
11
|
+
export interface ApexRuleNode {
|
|
12
|
+
getChild(i: number): unknown;
|
|
13
|
+
getChildCount(): number;
|
|
14
|
+
getText(): string;
|
|
15
|
+
parentCtx?: ApexRuleNode | undefined;
|
|
16
|
+
start?: {
|
|
17
|
+
column?: number;
|
|
18
|
+
line?: number;
|
|
19
|
+
text?: string;
|
|
20
|
+
};
|
|
21
|
+
stop?: {
|
|
22
|
+
column?: number;
|
|
23
|
+
line?: number;
|
|
24
|
+
text?: string;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export declare function parseApexSource(source: string): CompilationUnitContext;
|
|
28
|
+
export declare function locOfCtx(ctx: ApexRuleNode): {
|
|
29
|
+
endCol: number;
|
|
30
|
+
endRow: number;
|
|
31
|
+
startCol: number;
|
|
32
|
+
startRow: number;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Walk an ANTLR parse-tree node depth-first, invoking the visitor on every
|
|
36
|
+
* descendant (including the start node). Terminal/error nodes have no
|
|
37
|
+
* children and are visited like any other node — caller filters by type.
|
|
38
|
+
*/
|
|
39
|
+
export declare function walkParseTree(node: ApexRuleNode, visit: (n: ApexRuleNode) => void): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ApexParserFactory, } from '@apexdevtools/apex-parser';
|
|
2
|
+
export function parseApexSource(source) {
|
|
3
|
+
const parser = ApexParserFactory.createParser(source);
|
|
4
|
+
return parser.compilationUnit();
|
|
5
|
+
}
|
|
6
|
+
export function locOfCtx(ctx) {
|
|
7
|
+
const { start } = ctx;
|
|
8
|
+
const stop = ctx.stop ?? ctx.start;
|
|
9
|
+
return {
|
|
10
|
+
endCol: (stop?.column ?? 0) + (stop?.text?.length ?? 0),
|
|
11
|
+
endRow: stop?.line ?? 0,
|
|
12
|
+
startCol: start?.column ?? 0,
|
|
13
|
+
startRow: start?.line ?? 0,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Walk an ANTLR parse-tree node depth-first, invoking the visitor on every
|
|
18
|
+
* descendant (including the start node). Terminal/error nodes have no
|
|
19
|
+
* children and are visited like any other node — caller filters by type.
|
|
20
|
+
*/
|
|
21
|
+
export function walkParseTree(node, visit) {
|
|
22
|
+
visit(node);
|
|
23
|
+
const childCount = typeof node.getChildCount === 'function' ? node.getChildCount() : 0;
|
|
24
|
+
for (let i = 0; i < childCount; i++) {
|
|
25
|
+
const c = node.getChild(i);
|
|
26
|
+
// Filter to ParserRuleContext children only — terminals don't carry rules.
|
|
27
|
+
if (c && typeof c.getChildCount === 'function') {
|
|
28
|
+
walkParseTree(c, visit);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=apex-parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apex-parse.js","sourceRoot":"","sources":["../../src/analyzer/apex-parse.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,GAElB,MAAM,2BAA2B,CAAC;AAoBnC,MAAM,UAAU,eAAe,CAAC,MAAc;IAC5C,MAAM,MAAM,GAAG,iBAAiB,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IACtD,OAAO,MAAM,CAAC,eAAe,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAiB;IACxC,MAAM,EAAC,KAAK,EAAC,GAAG,GAAG,CAAC;IACpB,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC;IACnC,OAAO;QACL,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC;QACvD,MAAM,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;QACvB,QAAQ,EAAE,KAAK,EAAE,MAAM,IAAI,CAAC;QAC5B,QAAQ,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC;KAC3B,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,IAAkB,EAClB,KAAgC;IAEhC,KAAK,CAAC,IAAI,CAAC,CAAC;IACZ,MAAM,UAAU,GACd,OAAO,IAAI,CAAC,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACtE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QACpC,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC3B,2EAA2E;QAC3E,IAAI,CAAC,IAAI,OAAQ,CAAkB,CAAC,aAAa,KAAK,UAAU,EAAE,CAAC;YACjE,aAAa,CAAC,CAAiB,EAAE,KAAK,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare function extractApexClassName(uri: string): string | undefined;
|
|
2
|
+
export interface ResolveOptions {
|
|
3
|
+
/**
|
|
4
|
+
* The .agent file that holds the apex:// reference. Used as the seed for
|
|
5
|
+
* the upward walk.
|
|
6
|
+
*/
|
|
7
|
+
agentFilePath: string;
|
|
8
|
+
/**
|
|
9
|
+
* Optional `--apex-source` override. When set, classes are looked up here
|
|
10
|
+
* before falling back to the upward walk.
|
|
11
|
+
*/
|
|
12
|
+
apexSourceOverride?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The CLI `--source-dir` value. We never walk above this directory.
|
|
15
|
+
*/
|
|
16
|
+
sourceDirRoot: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Find `classes/<ClassName>.cls` by walking up from the .agent file. The walk
|
|
20
|
+
* stops at the CLI's source-dir root so we never escape it. Returns the
|
|
21
|
+
* absolute path or undefined.
|
|
22
|
+
*
|
|
23
|
+
* Path conventions covered:
|
|
24
|
+
* - sfdx default: <pkg>/main/default/classes/<X>.cls, with the .agent in
|
|
25
|
+
* <pkg>/main/default/aiAuthoringBundles/<Bundle>/<X>.agent
|
|
26
|
+
* - flat fixtures: <root>/classes/<X>.cls, with .agent under
|
|
27
|
+
* <root>/aiAuthoringBundles/<Bundle>/<X>.agent
|
|
28
|
+
* - explicit override: --apex-source <dir>/<X>.cls
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveApexClassPath(className: string, opts: ResolveOptions): Promise<string | undefined>;
|
|
31
|
+
/** True if `child` is the same as or a subpath of `parent` (path-segment safe). */
|
|
32
|
+
export declare function isUnder(child: string, parent: string): boolean;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
|
|
3
|
+
const APEX_SCHEME = /^apex:\/\/(.+)$/;
|
|
4
|
+
export function extractApexClassName(uri) {
|
|
5
|
+
const m = APEX_SCHEME.exec(uri);
|
|
6
|
+
if (!m)
|
|
7
|
+
return undefined;
|
|
8
|
+
// `apex://Foo` or `apex://namespace__Foo`; take the trailing simple name.
|
|
9
|
+
const raw = m[1].trim();
|
|
10
|
+
const dot = raw.lastIndexOf('.');
|
|
11
|
+
return dot === -1 ? raw : raw.slice(dot + 1);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Find `classes/<ClassName>.cls` by walking up from the .agent file. The walk
|
|
15
|
+
* stops at the CLI's source-dir root so we never escape it. Returns the
|
|
16
|
+
* absolute path or undefined.
|
|
17
|
+
*
|
|
18
|
+
* Path conventions covered:
|
|
19
|
+
* - sfdx default: <pkg>/main/default/classes/<X>.cls, with the .agent in
|
|
20
|
+
* <pkg>/main/default/aiAuthoringBundles/<Bundle>/<X>.agent
|
|
21
|
+
* - flat fixtures: <root>/classes/<X>.cls, with .agent under
|
|
22
|
+
* <root>/aiAuthoringBundles/<Bundle>/<X>.agent
|
|
23
|
+
* - explicit override: --apex-source <dir>/<X>.cls
|
|
24
|
+
*/
|
|
25
|
+
export async function resolveApexClassPath(className, opts) {
|
|
26
|
+
const candidates = [];
|
|
27
|
+
if (opts.apexSourceOverride) {
|
|
28
|
+
const override = isAbsolute(opts.apexSourceOverride)
|
|
29
|
+
? opts.apexSourceOverride
|
|
30
|
+
: resolve(opts.apexSourceOverride);
|
|
31
|
+
candidates.push(join(override, `${className}.cls`), join(override, 'classes', `${className}.cls`));
|
|
32
|
+
}
|
|
33
|
+
const stop = resolve(opts.sourceDirRoot);
|
|
34
|
+
let dir = dirname(resolve(opts.agentFilePath));
|
|
35
|
+
while (true) {
|
|
36
|
+
candidates.push(join(dir, 'classes', `${className}.cls`));
|
|
37
|
+
if (dir === stop)
|
|
38
|
+
break;
|
|
39
|
+
const parent = dirname(dir);
|
|
40
|
+
if (parent === dir)
|
|
41
|
+
break;
|
|
42
|
+
dir = parent;
|
|
43
|
+
}
|
|
44
|
+
for (const c of candidates) {
|
|
45
|
+
const s = await stat(c).catch(() => { });
|
|
46
|
+
if (s?.isFile())
|
|
47
|
+
return c;
|
|
48
|
+
}
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
/** True if `child` is the same as or a subpath of `parent` (path-segment safe). */
|
|
52
|
+
export function isUnder(child, parent) {
|
|
53
|
+
const c = resolve(child);
|
|
54
|
+
const p = resolve(parent);
|
|
55
|
+
if (c === p)
|
|
56
|
+
return true;
|
|
57
|
+
return c.startsWith(p.endsWith(sep) ? p : p + sep);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=apex-resolve.js.map
|