phpstan-mcp-mx 0.17.0 → 0.18.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/CHANGELOG.md +10 -4
- package/README.md +45 -49
- package/dist/baseline.js +15 -11
- package/dist/baseline.js.map +1 -1
- package/dist/changed.js +11 -11
- package/dist/changed.js.map +1 -1
- package/dist/config.js +2 -2
- package/dist/config.js.map +1 -1
- package/dist/defaults.js +11 -11
- package/dist/defaults.js.map +1 -1
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/phpstan/output.js +2 -2
- package/dist/phpstan/output.js.map +1 -1
- package/dist/phpstan/parameters.js +10 -10
- package/dist/phpstan/parameters.js.map +1 -1
- package/dist/phpstan/process.js +2 -2
- package/dist/phpstan/process.js.map +1 -1
- package/dist/phpstan/queue.js +3 -3
- package/dist/phpstan/queue.js.map +1 -1
- package/dist/phpstan.js +40 -40
- package/dist/phpstan.js.map +1 -1
- package/dist/project.js +12 -12
- package/dist/project.js.map +1 -1
- package/dist/report.js +31 -31
- package/dist/report.js.map +1 -1
- package/dist/roots.js +3 -3
- package/dist/roots.js.map +1 -1
- package/dist/runner.js +10 -10
- package/dist/runner.js.map +1 -1
- package/dist/server.js +3 -3
- package/dist/server.js.map +1 -1
- package/dist/tmpdir.js +4 -4
- package/dist/tmpdir.js.map +1 -1
- package/dist/tools/analysis.js +38 -38
- package/dist/tools/analysis.js.map +1 -1
- package/dist/tools/inspect.js +85 -84
- package/dist/tools/inspect.js.map +1 -1
- package/dist/tools/prompts.js +163 -162
- package/dist/tools/prompts.js.map +1 -1
- package/dist/tools/shared.js +38 -38
- package/dist/tools/shared.js.map +1 -1
- package/dist/tools/writes.js +23 -23
- package/dist/tools/writes.js.map +1 -1
- package/dist/unmatched.js +31 -30
- package/dist/unmatched.js.map +1 -1
- package/dist/version.d.ts +17 -4
- package/dist/version.js +27 -4
- package/dist/version.js.map +1 -1
- package/package.json +1 -2
package/dist/tools/analysis.js
CHANGED
|
@@ -7,21 +7,21 @@ import { analysisShape, common, configArgument, projectArgument, readOnly, runAn
|
|
|
7
7
|
/** Hangt `analyse_project` aan de server: PHPStan over (een deel van) een project. */
|
|
8
8
|
export function registerAnalyseProject(server, roots, runs) {
|
|
9
9
|
server.registerTool('analyse_project', {
|
|
10
|
-
title: '
|
|
11
|
-
description: '
|
|
12
|
-
'
|
|
10
|
+
title: 'Analyse project',
|
|
11
|
+
description: 'Runs PHPStan over (part of) a project and returns the findings. ' +
|
|
12
|
+
'Without `paths`, it analyses what phpstan.neon specifies.',
|
|
13
13
|
inputSchema: {
|
|
14
14
|
project: projectArgument,
|
|
15
15
|
paths: z
|
|
16
16
|
.array(z.string())
|
|
17
17
|
.optional()
|
|
18
|
-
.describe('
|
|
19
|
-
level: z.string().optional().describe('Level
|
|
18
|
+
.describe('Paths inside the project; empty means: follow the configuration'),
|
|
19
|
+
level: z.string().optional().describe('Level override, 0 to 10 or "max"'),
|
|
20
20
|
config: configArgument,
|
|
21
21
|
memoryLimit: z
|
|
22
22
|
.string()
|
|
23
23
|
.optional()
|
|
24
|
-
.describe('
|
|
24
|
+
.describe('E.g. "1G", if PHPStan hits the PHP memory limit; overrides the server default'),
|
|
25
25
|
timeoutSeconds: z.number().int().positive().max(1800).optional(),
|
|
26
26
|
...windowShape,
|
|
27
27
|
},
|
|
@@ -50,30 +50,30 @@ export function registerAnalyseProject(server, roots, runs) {
|
|
|
50
50
|
/** Hangt `analyse_changed` aan de server: alleen wat git gewijzigd heeft. */
|
|
51
51
|
export function registerAnalyseChanged(server, roots, runs) {
|
|
52
52
|
server.registerTool('analyse_changed', {
|
|
53
|
-
title: '
|
|
54
|
-
description: '
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
|
|
58
|
-
'
|
|
59
|
-
'`notices`
|
|
60
|
-
'
|
|
53
|
+
title: 'Analyse changed files',
|
|
54
|
+
description: 'Runs PHPStan over the PHP files in this project that git reports as changed, ' +
|
|
55
|
+
'including files that have not been added yet. Meant to check whether your own ' +
|
|
56
|
+
'change introduces errors, without analysing the whole project. Compares against ' +
|
|
57
|
+
`${DEFAULT_BASE} by default; pass \`base\` (e.g. "main") for the whole branch. ` +
|
|
58
|
+
'Each finding says with `onChangedLine` whether it is on a changed line, and ' +
|
|
59
|
+
'`notices` reports what the change hides: new `@phpstan-ignore` comments and errors ' +
|
|
60
|
+
'added to the baseline.',
|
|
61
61
|
inputSchema: {
|
|
62
62
|
project: projectArgument,
|
|
63
63
|
base: z
|
|
64
64
|
.string()
|
|
65
65
|
.optional()
|
|
66
|
-
.describe(`Commit, branch
|
|
66
|
+
.describe(`Commit, branch or tag to compare against (default ${DEFAULT_BASE})`),
|
|
67
67
|
staged: z
|
|
68
68
|
.boolean()
|
|
69
69
|
.optional()
|
|
70
|
-
.describe('
|
|
70
|
+
.describe('Only what is staged (git diff --cached), for a check before a commit'),
|
|
71
71
|
onlyChangedLines: z
|
|
72
72
|
.boolean()
|
|
73
73
|
.optional()
|
|
74
|
-
.describe('
|
|
75
|
-
'
|
|
76
|
-
level: z.string().optional().describe('Level
|
|
74
|
+
.describe('Only findings on changed lines. The counts still cover everything, so what ' +
|
|
75
|
+
'is elsewhere in the files stays visible'),
|
|
76
|
+
level: z.string().optional().describe('Level override, 0 to 10 or "max"'),
|
|
77
77
|
config: configArgument,
|
|
78
78
|
memoryLimit: z.string().optional(),
|
|
79
79
|
timeoutSeconds: z.number().int().positive().max(1800).optional(),
|
|
@@ -109,26 +109,26 @@ export function registerAnalyseChanged(server, roots, runs) {
|
|
|
109
109
|
/** Hoeveel losse items een melding hoogstens noemt; de rest als aantal. */
|
|
110
110
|
const MAX_HIDDEN_ITEMS = 10;
|
|
111
111
|
/**
|
|
112
|
-
* Wat de wijziging verstopt, als meldingen onder "
|
|
112
|
+
* Wat de wijziging verstopt, als meldingen onder "Note". Zonder oordeel: een
|
|
113
113
|
* ignore kan terecht zijn. Het gaat erom dat het niet stil gebeurt — `analyse_changed`
|
|
114
114
|
* zag een nieuwe fout wel, maar een fout die in dezelfde wijziging werd weggezet niet.
|
|
115
115
|
*/
|
|
116
116
|
function describeHidden(changed, baselines) {
|
|
117
117
|
const notices = [];
|
|
118
|
-
const tegen = changed.staged ? `in
|
|
118
|
+
const tegen = changed.staged ? `in what is staged relative to ${changed.base}` : `relative to ${changed.base}`;
|
|
119
119
|
if (changed.ignoresAdded.length > 0) {
|
|
120
120
|
const items = changed.ignoresAdded
|
|
121
121
|
.slice(0, MAX_HIDDEN_ITEMS)
|
|
122
122
|
.map((ignore) => `${ignore.path}:${String(ignore.line)} ${ignore.kind}${ignore.text === '' ? '' : ` ${ignore.text}`}`);
|
|
123
123
|
const rest = changed.ignoresAdded.length - items.length;
|
|
124
|
-
notices.push(`${String(changed.ignoresAdded.length)}
|
|
125
|
-
(rest > 0 ? `;
|
|
126
|
-
'.
|
|
124
|
+
notices.push(`${String(changed.ignoresAdded.length)} new @phpstan-ignore comment(s) ${tegen}: ${items.join('; ')}` +
|
|
125
|
+
(rest > 0 ? `; and ${String(rest)} more` : '') +
|
|
126
|
+
'. What they ignore is not in this report.');
|
|
127
127
|
}
|
|
128
128
|
for (const baseline of baselines) {
|
|
129
129
|
const erbij = baseline.added.reduce((som, entry) => som + entry.count - entry.was, 0);
|
|
130
130
|
if (erbij === 0) {
|
|
131
|
-
notices.push(`${baseline.file}: ${String(baseline.removed)}
|
|
131
|
+
notices.push(`${baseline.file}: ${String(baseline.removed)} ignored error(s) removed ${tegen}, none added.`);
|
|
132
132
|
continue;
|
|
133
133
|
}
|
|
134
134
|
const items = baseline.added
|
|
@@ -137,25 +137,25 @@ function describeHidden(changed, baselines) {
|
|
|
137
137
|
? `${entry.identifier} in ${entry.path} (${String(entry.count)})`
|
|
138
138
|
: `${entry.identifier} in ${entry.path} (${String(entry.was)}→${String(entry.count)})`);
|
|
139
139
|
const rest = baseline.added.length - items.length;
|
|
140
|
-
notices.push(`${baseline.file}: ${String(erbij)}
|
|
141
|
-
(rest > 0 ? `;
|
|
142
|
-
(baseline.removed > 0 ? `. ${String(baseline.removed)}
|
|
140
|
+
notices.push(`${baseline.file}: ${String(erbij)} ignored error(s) added ${tegen}: ${items.join('; ')}` +
|
|
141
|
+
(rest > 0 ? `; and ${String(rest)} more entr${rest === 1 ? 'y' : 'ies'}` : '') +
|
|
142
|
+
(baseline.removed > 0 ? `. ${String(baseline.removed)} removed.` : '.'));
|
|
143
143
|
}
|
|
144
144
|
return notices;
|
|
145
145
|
}
|
|
146
146
|
/** Waarom er niets geanalyseerd is; de overgeslagen paden horen daar bij. */
|
|
147
147
|
function describeNothingChanged(changed) {
|
|
148
|
-
const wat = changed.staged ? '
|
|
149
|
-
const lines = [`
|
|
148
|
+
const wat = changed.staged ? 'staged' : 'changed';
|
|
149
|
+
const lines = [`No ${wat} PHP files relative to ${changed.base}.`];
|
|
150
150
|
if (changed.skipped.length > 0)
|
|
151
151
|
lines.push('', ...skippedLines(changed));
|
|
152
152
|
return lines.join('\n');
|
|
153
153
|
}
|
|
154
154
|
/** Wat er geanalyseerd is, zodat zichtbaar is waarop het rapport eronder betrekking heeft. */
|
|
155
155
|
function describeChanged(resolved, changed) {
|
|
156
|
-
const waar = changed.staged ? '
|
|
156
|
+
const waar = changed.staged ? 'staged relative to' : 'changed relative to';
|
|
157
157
|
const lines = [
|
|
158
|
-
`${String(changed.files.length)} PHP
|
|
158
|
+
`${String(changed.files.length)} PHP file(s) ${waar} ${changed.base}:`,
|
|
159
159
|
...changed.files.map((file) => ` ${displayPath(resolved.path, file)}`),
|
|
160
160
|
];
|
|
161
161
|
if (changed.skipped.length > 0)
|
|
@@ -164,20 +164,20 @@ function describeChanged(resolved, changed) {
|
|
|
164
164
|
}
|
|
165
165
|
function skippedLines(changed) {
|
|
166
166
|
return [
|
|
167
|
-
'
|
|
167
|
+
'Skipped, so not included in this report:',
|
|
168
168
|
...changed.skipped.map((entry) => ` ${entry.path}: ${entry.reason}`),
|
|
169
169
|
];
|
|
170
170
|
}
|
|
171
171
|
/** Hangt `analyse_file` aan de server: één bestand of map binnen een project. */
|
|
172
172
|
export function registerAnalyseFile(server, roots, runs) {
|
|
173
173
|
server.registerTool('analyse_file', {
|
|
174
|
-
title: '
|
|
175
|
-
description: '
|
|
176
|
-
'
|
|
174
|
+
title: 'Analyse file or directory',
|
|
175
|
+
description: 'Runs PHPStan over a single file or directory inside a project. The path may be ' +
|
|
176
|
+
'relative to the project root or absolute, but must be inside the project.',
|
|
177
177
|
inputSchema: {
|
|
178
178
|
project: projectArgument,
|
|
179
|
-
path: z.string().describe('
|
|
180
|
-
level: z.string().optional().describe('Level
|
|
179
|
+
path: z.string().describe('File or directory inside the project, e.g. "app/Models/User.php"'),
|
|
180
|
+
level: z.string().optional().describe('Level override, 0 to 10 or "max"'),
|
|
181
181
|
config: configArgument,
|
|
182
182
|
memoryLimit: z.string().optional(),
|
|
183
183
|
timeoutSeconds: z.number().int().positive().max(1800).optional(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analysis.js","sourceRoot":"","sources":["../../src/tools/analysis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChG,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG9C,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,OAAO,EACL,aAAa,EACb,MAAM,EACN,cAAc,EACd,eAAe,EACf,QAAQ,EACR,WAAW,EACX,MAAM,EACN,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,sFAAsF;AACtF,MAAM,UAAU,sBAAsB,CAAC,MAAiB,EAAE,KAAa,EAAE,IAAc;IACrF,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"analysis.js","sourceRoot":"","sources":["../../src/tools/analysis.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/inspect.js
CHANGED
|
@@ -15,10 +15,10 @@ import { writesAllowed } from './writes.js';
|
|
|
15
15
|
/** Hangt `list_projects` aan de server: welke projecten binnen bereik liggen. */
|
|
16
16
|
export function registerListProjects(server, roots) {
|
|
17
17
|
server.registerTool('list_projects', {
|
|
18
|
-
title: 'PHP
|
|
19
|
-
description: '
|
|
20
|
-
'PHPStan
|
|
21
|
-
'
|
|
18
|
+
title: 'List PHP projects',
|
|
19
|
+
description: 'Lists the PHP projects this server may analyse, with for each project whether ' +
|
|
20
|
+
'PHPStan is installed and which config file is present. Use the `path` from the ' +
|
|
21
|
+
'result as the `project` argument for the other tools.',
|
|
22
22
|
inputSchema: {},
|
|
23
23
|
outputSchema: {
|
|
24
24
|
projects: z.array(z.object({
|
|
@@ -26,13 +26,13 @@ export function registerListProjects(server, roots) {
|
|
|
26
26
|
root: z.string(),
|
|
27
27
|
hasPhpstan: z.boolean(),
|
|
28
28
|
config: nullable(z.string()),
|
|
29
|
-
hasGit: z.boolean().describe('
|
|
30
|
-
runner: nullable(z.string()).describe('
|
|
29
|
+
hasGit: z.boolean().describe('Whether analyse_changed can be used here'),
|
|
30
|
+
runner: nullable(z.string()).describe('Command PHPStan runs through here, or null for the host'),
|
|
31
31
|
defaults: nullable(z.object({
|
|
32
32
|
memoryLimit: nullable(z.string()),
|
|
33
33
|
timeoutSeconds: nullable(z.number()),
|
|
34
34
|
config: nullable(z.string()),
|
|
35
|
-
})).describe('
|
|
35
|
+
})).describe('Defaults the server sets for this project; a tool argument takes precedence'),
|
|
36
36
|
})),
|
|
37
37
|
problems: z.array(z.object({ root: z.string(), problem: z.string() })),
|
|
38
38
|
},
|
|
@@ -42,9 +42,9 @@ export function registerListProjects(server, roots) {
|
|
|
42
42
|
// PHPStan hier niet eens gestart is. Juist bij deze tool — het beginpunt — moet
|
|
43
43
|
// een aanroeper kunnen zien dat het aan de serverconfiguratie ligt.
|
|
44
44
|
if (roots.length === 0) {
|
|
45
|
-
return failure(`
|
|
46
|
-
'MCP
|
|
47
|
-
'
|
|
45
|
+
return failure(`No project directories are configured. Set ${ROOTS_ENV_VAR} in the ` +
|
|
46
|
+
'MCP server configuration to the absolute paths that may be analysed, ' +
|
|
47
|
+
'separated by colons.', 'no_roots_configured', { hint: `Set ${ROOTS_ENV_VAR}, or "roots" in the config file.` });
|
|
48
48
|
}
|
|
49
49
|
const projects = discoverProjects(roots);
|
|
50
50
|
const problems = roots
|
|
@@ -67,31 +67,31 @@ export function registerListProjects(server, roots) {
|
|
|
67
67
|
function renderProjects(projects, problems) {
|
|
68
68
|
const lines = [];
|
|
69
69
|
if (projects.length === 0) {
|
|
70
|
-
lines.push('
|
|
70
|
+
lines.push('No PHP projects found under the configured directories.');
|
|
71
71
|
}
|
|
72
72
|
else {
|
|
73
|
-
lines.push(`${projects.length} project(
|
|
73
|
+
lines.push(`${projects.length} project(s):`);
|
|
74
74
|
for (const project of projects) {
|
|
75
|
-
const config = project.config ?? '
|
|
76
|
-
const phpstan = project.hasPhpstan ? 'phpstan
|
|
77
|
-
const git = project.hasGit ? 'git' : '
|
|
75
|
+
const config = project.config ?? 'no config';
|
|
76
|
+
const phpstan = project.hasPhpstan ? 'phpstan present' : 'phpstan NOT installed';
|
|
77
|
+
const git = project.hasGit ? 'git' : 'no git';
|
|
78
78
|
const waar = project.runner === null ? '' : `, via ${project.runner}`;
|
|
79
|
-
const standaard = project.defaults === null ? '' : `,
|
|
79
|
+
const standaard = project.defaults === null ? '' : `, defaults ${describeDefaults(project.defaults)}`;
|
|
80
80
|
lines.push(`- ${project.path} (${phpstan}, ${config}, ${git}${waar}${standaard})`);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
if (problems.length > 0) {
|
|
84
|
-
lines.push('', '
|
|
84
|
+
lines.push('', 'Skipped entries from the configuration:');
|
|
85
85
|
for (const problem of problems)
|
|
86
86
|
lines.push(`- ${problem.root}: ${problem.problem}`);
|
|
87
87
|
}
|
|
88
88
|
return lines.join('\n');
|
|
89
89
|
}
|
|
90
|
-
/** "
|
|
90
|
+
/** "memory 2G, timeout 600s" — alleen wat gezet is, in een vaste volgorde. */
|
|
91
91
|
function describeDefaults(defaults) {
|
|
92
92
|
const delen = [];
|
|
93
93
|
if (defaults.memoryLimit !== null)
|
|
94
|
-
delen.push(`
|
|
94
|
+
delen.push(`memory ${defaults.memoryLimit}`);
|
|
95
95
|
if (defaults.timeoutSeconds !== null)
|
|
96
96
|
delen.push(`timeout ${String(defaults.timeoutSeconds)}s`);
|
|
97
97
|
if (defaults.config !== null)
|
|
@@ -105,24 +105,24 @@ function describeDefaults(defaults) {
|
|
|
105
105
|
*/
|
|
106
106
|
export function registerServerConfig(server, roots) {
|
|
107
107
|
server.registerTool('server_config', {
|
|
108
|
-
title: '
|
|
109
|
-
description: '
|
|
110
|
-
'
|
|
111
|
-
'
|
|
112
|
-
'
|
|
108
|
+
title: 'Server settings',
|
|
109
|
+
description: 'Returns the settings this server runs with: which project directories are in ' +
|
|
110
|
+
'scope, the default memory limit, how many analyses may run in parallel, where ' +
|
|
111
|
+
'temporary files go and whether writing tools are enabled. Use this when something ' +
|
|
112
|
+
'is refused or slower than expected, before you doubt the project.',
|
|
113
113
|
inputSchema: {},
|
|
114
114
|
outputSchema: {
|
|
115
115
|
version: z.string(),
|
|
116
116
|
roots: z.array(z.object({
|
|
117
|
-
raw: z.string().describe('
|
|
118
|
-
path: nullable(z.string()).describe('
|
|
117
|
+
raw: z.string().describe('As given in the environment variable'),
|
|
118
|
+
path: nullable(z.string()).describe('The resolved path, or null if the root is unusable'),
|
|
119
119
|
problem: nullable(z.string()),
|
|
120
120
|
})),
|
|
121
|
-
memoryLimit: nullable(z.string()).describe('
|
|
122
|
-
maxParallel: nullable(z.number()).describe('
|
|
121
|
+
memoryLimit: nullable(z.string()).describe('Default --memory-limit, or null if it is not set'),
|
|
122
|
+
maxParallel: nullable(z.number()).describe('How many analyses may run in parallel; null if the value was rejected'),
|
|
123
123
|
tmpDir: nullable(z.string()).describe('"project" of "shared"'),
|
|
124
|
-
writesAllowed: z.boolean().describe('
|
|
125
|
-
runners: z.array(z.string()).describe('
|
|
124
|
+
writesAllowed: z.boolean().describe('Whether generate_baseline is registered'),
|
|
125
|
+
runners: z.array(z.string()).describe('Projects with a runner (execution environment)'),
|
|
126
126
|
projectDefaults: z
|
|
127
127
|
.array(z.object({
|
|
128
128
|
project: z.string(),
|
|
@@ -130,10 +130,10 @@ export function registerServerConfig(server, roots) {
|
|
|
130
130
|
timeoutSeconds: nullable(z.number()),
|
|
131
131
|
config: nullable(z.string()),
|
|
132
132
|
}))
|
|
133
|
-
.describe('
|
|
133
|
+
.describe('Projects with per-project defaults; a tool argument still takes precedence'),
|
|
134
134
|
problems: z
|
|
135
135
|
.array(z.string())
|
|
136
|
-
.describe('
|
|
136
|
+
.describe('Settings that were rejected; those fields are then null'),
|
|
137
137
|
},
|
|
138
138
|
annotations: readOnly(),
|
|
139
139
|
}, () => {
|
|
@@ -178,23 +178,23 @@ function setting(read, problems) {
|
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
function renderConfig(config) {
|
|
181
|
-
const lines = [`phpstan-mcp ${config.version}`, '', '
|
|
181
|
+
const lines = [`phpstan-mcp ${config.version}`, '', 'Project directories:'];
|
|
182
182
|
if (config.roots.length === 0) {
|
|
183
|
-
lines.push(` (
|
|
183
|
+
lines.push(` (none; set ${ROOTS_ENV_VAR})`);
|
|
184
184
|
}
|
|
185
185
|
else {
|
|
186
186
|
for (const root of config.roots) {
|
|
187
187
|
lines.push(` ${root.path ?? root.raw}${root.problem === null ? '' : ` — ${root.problem}`}`);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
|
-
lines.push('', `
|
|
190
|
+
lines.push('', `Memory limit: ${config.memoryLimit ?? "not set (PHP's own limit applies)"}`, `Parallel: ${config.maxParallel === null ? 'unknown' : String(config.maxParallel)}`, `Temp directory: ${config.tmpDir === null ? 'unknown' : config.tmpDir}`, `Writing tools: ${config.writesAllowed ? 'on (generate_baseline exists)' : 'off'}`, `Runners: ${config.runners.length === 0 ? 'none' : config.runners.join(', ')}`);
|
|
191
191
|
if (config.projectDefaults.length > 0) {
|
|
192
|
-
lines.push('', '
|
|
192
|
+
lines.push('', 'Per-project defaults (a tool argument takes precedence):');
|
|
193
193
|
for (const entry of config.projectDefaults)
|
|
194
194
|
lines.push(` ${entry.project}: ${describeDefaults(entry)}`);
|
|
195
195
|
}
|
|
196
196
|
if (config.problems.length > 0) {
|
|
197
|
-
lines.push('', '
|
|
197
|
+
lines.push('', 'Rejected settings:');
|
|
198
198
|
for (const problem of config.problems)
|
|
199
199
|
lines.push(`- ${problem}`);
|
|
200
200
|
}
|
|
@@ -203,50 +203,50 @@ function renderConfig(config) {
|
|
|
203
203
|
/** Hangt `diagnose_project` aan de server: PHPStans eigen opstelling in een project. */
|
|
204
204
|
export function registerDiagnose(server, roots) {
|
|
205
205
|
server.registerTool('diagnose_project', {
|
|
206
|
-
title: 'PHPStan
|
|
207
|
-
description: '
|
|
208
|
-
'configs
|
|
209
|
-
'
|
|
210
|
-
'(
|
|
211
|
-
'
|
|
212
|
-
'
|
|
206
|
+
title: 'Inspect PHPStan setup',
|
|
207
|
+
description: 'Asks PHPStan itself for its setup in this project: PHP and PHPStan version, the ' +
|
|
208
|
+
'configs that Composer packages bring along (such as Larastan) and whether the turbo ' +
|
|
209
|
+
'extension is enabled — plus the configuration as PHPStan ends up using it: the level ' +
|
|
210
|
+
'(also when it is set in a config from vendor/), the paths, the number of ignore rules ' +
|
|
211
|
+
'and the temp directory. Does not run an analysis. Use this when a project behaves ' +
|
|
212
|
+
'differently from another project, before you start doubting the code.',
|
|
213
213
|
inputSchema: {
|
|
214
214
|
project: projectArgument,
|
|
215
215
|
timeoutSeconds: z.number().int().positive().max(1800).optional(),
|
|
216
216
|
},
|
|
217
217
|
outputSchema: {
|
|
218
218
|
project: z.string(),
|
|
219
|
-
output: z.string().describe('
|
|
219
|
+
output: z.string().describe("PHPStan's own text, unchanged"),
|
|
220
220
|
parameters: nullable(z.object({
|
|
221
221
|
level: nullable(z.union([z.number(), z.string()])),
|
|
222
222
|
paths: z.array(z.string()),
|
|
223
223
|
excludePaths: z.array(z.string()),
|
|
224
|
-
ignoreErrors: z.number().describe('
|
|
224
|
+
ignoreErrors: z.number().describe('Ignore rules in total: the baseline plus ignoreErrors from every config'),
|
|
225
225
|
reportUnmatchedIgnoredErrors: nullable(z.boolean()),
|
|
226
|
-
tmpDir: nullable(z.string()).describe('
|
|
226
|
+
tmpDir: nullable(z.string()).describe('Where PHPStan puts its cache: what clear_result_cache clears'),
|
|
227
227
|
bootstrapFiles: z.array(z.string()),
|
|
228
|
-
})).describe('
|
|
229
|
-
parametersProblem: nullable(z.string()).describe('
|
|
228
|
+
})).describe('The configuration as PHPStan ends up using it, including what comes from vendor/'),
|
|
229
|
+
parametersProblem: nullable(z.string()).describe('Why parameters is missing, e.g. a bootstrap that fails to start'),
|
|
230
230
|
},
|
|
231
231
|
annotations: readOnly(),
|
|
232
232
|
}, async (args, extra) => {
|
|
233
233
|
const begin = Date.now();
|
|
234
234
|
try {
|
|
235
235
|
const resolved = resolveProject(args.project, roots);
|
|
236
|
-
log(server, 'debug', {
|
|
236
|
+
log(server, 'debug', { event: 'diagnosis started', project: resolved.path });
|
|
237
237
|
const diagnosis = await diagnose(resolved, {
|
|
238
238
|
...(args.timeoutSeconds === undefined ? {} : { timeoutMs: args.timeoutSeconds * 1000 }),
|
|
239
239
|
signal: extra.signal,
|
|
240
240
|
});
|
|
241
241
|
log(server, 'info', {
|
|
242
|
-
|
|
242
|
+
event: 'diagnosis finished',
|
|
243
243
|
project: resolved.path,
|
|
244
|
-
|
|
244
|
+
durationMs: Date.now() - begin,
|
|
245
245
|
});
|
|
246
246
|
// PHPStans eigen tekst eerst en ongewijzigd; de samenvatting eronder, zodat
|
|
247
247
|
// duidelijk blijft wat van PHPStan komt en wat deze server erbij zet.
|
|
248
248
|
const aanvulling = diagnosis.parameters === null
|
|
249
|
-
? `
|
|
249
|
+
? `Effective configuration: not available. ${diagnosis.parametersProblem ?? ''}`.trim()
|
|
250
250
|
: renderParameters(diagnosis.parameters);
|
|
251
251
|
return {
|
|
252
252
|
content: [{ type: 'text', text: `${diagnosis.output}\n\n${aanvulling}` }],
|
|
@@ -261,27 +261,27 @@ export function registerDiagnose(server, roots) {
|
|
|
261
261
|
/** Hangt `baseline_summary` aan de server: wat de baseline verbergt. */
|
|
262
262
|
export function registerBaselineSummary(server, roots) {
|
|
263
263
|
server.registerTool('baseline_summary', {
|
|
264
|
-
title: '
|
|
265
|
-
description: '
|
|
266
|
-
'
|
|
267
|
-
'
|
|
268
|
-
'
|
|
269
|
-
'
|
|
264
|
+
title: 'Summarise baseline',
|
|
265
|
+
description: 'Reads the phpstan-baseline*.neon of a project and counts what is ignored, per ' +
|
|
266
|
+
'identifier and per file. Does not run an analysis. Note: what is in here is exactly ' +
|
|
267
|
+
'what analyse_project does NOT report — a project that includes its baseline only ' +
|
|
268
|
+
'reports the errors that are new. With `path`, also the entries for one file, with ' +
|
|
269
|
+
'identifier, count and pattern.',
|
|
270
270
|
inputSchema: {
|
|
271
271
|
project: projectArgument,
|
|
272
272
|
path: z
|
|
273
273
|
.string()
|
|
274
274
|
.optional()
|
|
275
|
-
.describe('
|
|
275
|
+
.describe('One file, relative to the project root: also return the baseline entries for just that file'),
|
|
276
276
|
},
|
|
277
277
|
outputSchema: {
|
|
278
278
|
project: z.string(),
|
|
279
|
-
hasBaseline: z.boolean().describe('False
|
|
279
|
+
hasBaseline: z.boolean().describe('False means: no baseline, so nothing is ignored'),
|
|
280
280
|
files: z.array(z.object({ file: z.string(), entries: z.number(), errors: z.number() })),
|
|
281
281
|
totals: z.object({ entries: z.number(), errors: z.number() }),
|
|
282
282
|
byIdentifier: z.array(z.object({ identifier: z.string(), count: z.number() })),
|
|
283
283
|
byPath: z.array(z.object({ path: z.string(), count: z.number() })),
|
|
284
|
-
entries: nullable(z.array(z.object({ file: z.string(), identifier: z.string(), count: z.number(), message: nullable(z.string()) }))).describe('
|
|
284
|
+
entries: nullable(z.array(z.object({ file: z.string(), identifier: z.string(), count: z.number(), message: nullable(z.string()) }))).describe('The entries for `path`; null if no path was given'),
|
|
285
285
|
},
|
|
286
286
|
annotations: readOnly(),
|
|
287
287
|
}, ({ project, path }) => {
|
|
@@ -309,41 +309,42 @@ const MAX_PATTERN_CHARS = 200;
|
|
|
309
309
|
/** De baseline-regels voor één bestand, als tekst. */
|
|
310
310
|
function renderEntries(path, entries) {
|
|
311
311
|
if (entries.length === 0)
|
|
312
|
-
return `
|
|
312
|
+
return `No baseline entries for ${path}.`;
|
|
313
313
|
const kort = (message) => message === null ? '' : message.length <= MAX_PATTERN_CHARS ? ` ${message}` : ` ${message.slice(0, MAX_PATTERN_CHARS)}…`;
|
|
314
314
|
return [
|
|
315
|
-
`${String(entries.length)} baseline
|
|
315
|
+
`${String(entries.length)} baseline entr${entries.length === 1 ? 'y' : 'ies'} for ${path}:`,
|
|
316
316
|
...entries.map((entry) => ` ${String(entry.count).padStart(3)} ${entry.identifier}${kort(entry.message)}`),
|
|
317
317
|
].join('\n');
|
|
318
318
|
}
|
|
319
319
|
/** Hangt `unmatched_ignores` aan de server: negeerregels die niets meer doen. */
|
|
320
320
|
export function registerUnmatchedIgnores(server, roots) {
|
|
321
321
|
server.registerTool('unmatched_ignores', {
|
|
322
|
-
title: '
|
|
323
|
-
description: '
|
|
324
|
-
'
|
|
325
|
-
'baseline
|
|
326
|
-
'
|
|
327
|
-
'
|
|
322
|
+
title: 'Find stale ignore rules',
|
|
323
|
+
description: 'Runs a full analysis with reportUnmatchedIgnoredErrors enabled and returns the ' +
|
|
324
|
+
'ignore rules that no longer match anything, or match less often than their count — ' +
|
|
325
|
+
'from the baseline and from ignoreErrors in the config. Such a rule belongs to an ' +
|
|
326
|
+
'error that has been fixed and can be removed. Does not change anything in the ' +
|
|
327
|
+
'project: the temporary config lives outside it — for a project with a runner, in ' +
|
|
328
|
+
"that runner's temp directory.",
|
|
328
329
|
inputSchema: {
|
|
329
330
|
project: projectArgument,
|
|
330
331
|
memoryLimit: z
|
|
331
332
|
.string()
|
|
332
333
|
.optional()
|
|
333
|
-
.describe('
|
|
334
|
+
.describe('E.g. "1G"; this is a full analysis, so it may need more'),
|
|
334
335
|
timeoutSeconds: z.number().int().positive().max(1800).optional(),
|
|
335
|
-
offset: z.number().int().min(0).optional().describe('
|
|
336
|
+
offset: z.number().int().min(0).optional().describe('Skip the first N entries'),
|
|
336
337
|
limit: z
|
|
337
338
|
.number()
|
|
338
339
|
.int()
|
|
339
340
|
.min(1)
|
|
340
341
|
.max(MAX_LIMIT)
|
|
341
342
|
.optional()
|
|
342
|
-
.describe(`
|
|
343
|
+
.describe(`How many entries this response contains (default ${String(DEFAULT_LIMIT)})`),
|
|
343
344
|
},
|
|
344
345
|
outputSchema: {
|
|
345
346
|
project: z.string(),
|
|
346
|
-
baselineFiles: z.array(z.string()).describe('
|
|
347
|
+
baselineFiles: z.array(z.string()).describe('The baseline files the config includes'),
|
|
347
348
|
totals: z.object({
|
|
348
349
|
unmatched: z.number(),
|
|
349
350
|
countTooHigh: z.number(),
|
|
@@ -351,25 +352,25 @@ export function registerUnmatchedIgnores(server, roots) {
|
|
|
351
352
|
general: z.number(),
|
|
352
353
|
otherFindings: z
|
|
353
354
|
.number()
|
|
354
|
-
.describe('
|
|
355
|
+
.describe('Regular findings from the same run; a new baseline would hide those too'),
|
|
355
356
|
}),
|
|
356
357
|
byPath: z.array(z.object({ path: z.string(), count: z.number() })),
|
|
357
358
|
entries: z.array(z.object({
|
|
358
|
-
path: z.string().describe('
|
|
359
|
+
path: z.string().describe('Path relative to the project root'),
|
|
359
360
|
identifier: nullable(z.string()),
|
|
360
361
|
pattern: z.string(),
|
|
361
362
|
kind: z.enum(['unmatched', 'countTooHigh']),
|
|
362
363
|
expected: nullable(z.number()),
|
|
363
364
|
occurred: nullable(z.number()),
|
|
364
|
-
inBaseline: nullable(z.boolean()).describe('
|
|
365
|
+
inBaseline: nullable(z.boolean()).describe('Whether the rule comes from an included baseline; null if that could not be read'),
|
|
365
366
|
})),
|
|
366
367
|
general: z
|
|
367
368
|
.array(z.string())
|
|
368
|
-
.describe('
|
|
369
|
+
.describe('Patterns without a path that no longer match anything; these are not in the baseline'),
|
|
369
370
|
generalErrors: z.array(z.string()),
|
|
370
371
|
warnings: z
|
|
371
372
|
.array(z.string())
|
|
372
|
-
.describe('
|
|
373
|
+
.describe('If this contains "Result is incomplete", PHPStan may have missed stale rules'),
|
|
373
374
|
notices: z.array(z.string()),
|
|
374
375
|
omitted: z.object({ general: z.number(), generalErrors: z.number(), warnings: z.number() }),
|
|
375
376
|
page: z.object({
|
|
@@ -385,18 +386,18 @@ export function registerUnmatchedIgnores(server, roots) {
|
|
|
385
386
|
const begin = Date.now();
|
|
386
387
|
try {
|
|
387
388
|
const resolved = resolveProject(args.project, roots);
|
|
388
|
-
log(server, 'debug', {
|
|
389
|
+
log(server, 'debug', { event: 'checking ignore rules', project: resolved.path });
|
|
389
390
|
const report = await findUnmatchedIgnores(resolved, {
|
|
390
391
|
...(args.memoryLimit === undefined ? {} : { memoryLimit: args.memoryLimit }),
|
|
391
392
|
...(args.timeoutSeconds === undefined ? {} : { timeoutMs: args.timeoutSeconds * 1000 }),
|
|
392
393
|
signal: extra.signal,
|
|
393
394
|
}, { offset: args.offset, limit: args.limit });
|
|
394
395
|
log(server, 'info', {
|
|
395
|
-
|
|
396
|
+
event: 'ignore rules checked',
|
|
396
397
|
project: resolved.path,
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
398
|
+
stale: report.totals.unmatched + report.totals.countTooHigh,
|
|
399
|
+
general: report.totals.general,
|
|
400
|
+
durationMs: Date.now() - begin,
|
|
400
401
|
});
|
|
401
402
|
return {
|
|
402
403
|
content: [{ type: 'text', text: renderUnmatched(report) }],
|