phpstan-mcp-mx 0.23.0 → 0.24.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.
@@ -0,0 +1,92 @@
1
+ import type { AnalyseOptions, AnalysisResult } from './phpstan.js';
2
+ import type { ResolvedProject } from './project.js';
3
+ /** Eén entry uit een baseline-bestand, met de regels waar hij staat. */
4
+ export interface BaselineBlock {
5
+ /** Index van de `-`-regel. */
6
+ readonly start: number;
7
+ /** Exclusief; de lege regels erna horen erbij, zodat weghalen geen gat achterlaat. */
8
+ readonly end: number;
9
+ /** Of deze parser de vorm kent. Een onbekend blok telt als grens, maar wordt nooit aangeraakt. */
10
+ readonly known: boolean;
11
+ readonly path: string | null;
12
+ readonly identifier: string | null;
13
+ /** Zoals PHPStan de regel noemt, zie StaleRule; null bij een regel met alleen een identifier. */
14
+ readonly pattern: string | null;
15
+ readonly count: number;
16
+ /** De regel met `count:`, of null als die ontbreekt (dan geldt 1). */
17
+ readonly countLine: number | null;
18
+ }
19
+ /** Een baseline-bestand in regels, met de entries die erin herkend zijn. */
20
+ export interface ParsedBaseline {
21
+ readonly lines: readonly string[];
22
+ readonly eol: string;
23
+ /** De regel met `ignoreErrors:`, of null als die er niet is. */
24
+ readonly listLine: number | null;
25
+ readonly blocks: readonly BaselineBlock[];
26
+ }
27
+ /** Wat `prune_baseline` gedaan heeft. */
28
+ export interface PruneResult {
29
+ readonly project: string;
30
+ readonly files: readonly {
31
+ file: string;
32
+ removed: number;
33
+ lowered: number;
34
+ errorsBefore: number;
35
+ errorsAfter: number;
36
+ }[];
37
+ readonly totals: {
38
+ /** Entries die weg zijn, inclusief die voor een bestand dat niet meer bestaat. */
39
+ readonly removed: number;
40
+ readonly lowered: number;
41
+ /** Hoeveel van `removed` naar een bestand wezen dat er niet meer is. */
42
+ readonly missingFiles: number;
43
+ readonly errorsBefore: number;
44
+ readonly errorsAfter: number;
45
+ };
46
+ /** Verouderde regels die deze tool bewust laat staan. */
47
+ readonly left: {
48
+ /** Met een pad, maar niet in een baseline die de config includeert: in de config zelf. */
49
+ readonly outsideBaseline: number;
50
+ /** Zonder pad: uit de ignoreErrors van een config, vaak een gedeelde in vendor/. */
51
+ readonly general: number;
52
+ /** Een te hoge count waarvan PHPStan niet zei hoe vaak hij nog voorkomt. */
53
+ readonly unknownCount: number;
54
+ /** Inline ignores in de code die niets meer onderdrukken: broncode, geen baseline. */
55
+ readonly inline: number;
56
+ };
57
+ /** Bevindingen uit dezelfde run: die zijn niet toegevoegd en blijven gemeld. */
58
+ readonly currentFindings: number;
59
+ readonly notices: readonly string[];
60
+ }
61
+ /** De analyse, apart zodat de tests hem kunnen vervangen zonder PHPStan te starten. */
62
+ export type UnmatchedAnalysis = (project: ResolvedProject, configName: string, options: Pick<AnalyseOptions, 'memoryLimit' | 'timeoutMs' | 'signal'>) => Promise<AnalysisResult>;
63
+ /**
64
+ * Snoeit de baselines die de config includeert: entries die niets meer matchen gaan eruit,
65
+ * een te hoge `count` gaat omlaag naar wat nog voorkomt. Er komt nooit iets bij.
66
+ *
67
+ * Dat is het verschil met `generate_baseline`: die ruimt ook op, maar zet alles wat nu
68
+ * gemeld wordt erbij, en dan verdwijnt het uit elke volgende analyse. Opruimen en verstoppen
69
+ * horen twee handelingen te zijn.
70
+ *
71
+ * Geen verse baseline naast de oude leggen: zolang de oude geïncludeerd is, negeert PHPStan
72
+ * die fouten al, en een verse baseline zou dan bijna leeg zijn. PHPStans eigen
73
+ * `reportUnmatchedIgnoredErrors` zegt per entry wat er niet meer klopt; dat is dezelfde run
74
+ * als `unmatched_ignores`.
75
+ *
76
+ * Alles wat niet eenduidig terug te vinden is, blijft staan. Een fout in het koppelen laat
77
+ * dus hooguit een verouderde entry achter, en haalt nooit een entry weg die nog iets doet.
78
+ */
79
+ export declare function pruneBaseline(project: ResolvedProject, options?: Pick<AnalyseOptions, 'memoryLimit' | 'timeoutMs' | 'signal'>, analyse?: UnmatchedAnalysis): Promise<PruneResult>;
80
+ /**
81
+ * Leest een baseline in blokken, met regelnummers, zodat een wijziging alleen die blokken
82
+ * raakt en de rest van het bestand byte voor byte blijft staan. Regelgericht, net als
83
+ * `parseBaseline`: PHPStan genereert dit bestand, dus de vorm ligt vast.
84
+ */
85
+ export declare function parseBaselineBlocks(content: string): ParsedBaseline;
86
+ /**
87
+ * Past de wijzigingen toe en geeft de nieuwe inhoud. Weghalen gaat per blok inclusief de
88
+ * lege regels erna; een lagere count vervangt alleen het getal op zijn eigen regel.
89
+ */
90
+ export declare function applyEdits(parsed: ParsedBaseline, remove: ReadonlySet<BaselineBlock>, lower: ReadonlyMap<BaselineBlock, number>): string;
91
+ /** Schrijft een overzicht van wat er gedaan is, als tekst voor het model. */
92
+ export declare function renderPruned(result: PruneResult): string;
package/dist/prune.js ADDED
@@ -0,0 +1,387 @@
1
+ import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join, normalize, resolve } from 'node:path';
3
+ import { BaselineError, includedBaselineFiles, unquote } from './baseline.js';
4
+ import { PhpstanError } from './phpstan.js';
5
+ import { analyseUnmatched, readStaleInline, readStaleRule, unmatchedGeneralPattern } from './unmatched.js';
6
+ /** Tekens die van een pad een fnmatch-patroon maken; zo'n pad bestaat nooit letterlijk. */
7
+ const GLOB = /[*?[\]]/;
8
+ /**
9
+ * Snoeit de baselines die de config includeert: entries die niets meer matchen gaan eruit,
10
+ * een te hoge `count` gaat omlaag naar wat nog voorkomt. Er komt nooit iets bij.
11
+ *
12
+ * Dat is het verschil met `generate_baseline`: die ruimt ook op, maar zet alles wat nu
13
+ * gemeld wordt erbij, en dan verdwijnt het uit elke volgende analyse. Opruimen en verstoppen
14
+ * horen twee handelingen te zijn.
15
+ *
16
+ * Geen verse baseline naast de oude leggen: zolang de oude geïncludeerd is, negeert PHPStan
17
+ * die fouten al, en een verse baseline zou dan bijna leeg zijn. PHPStans eigen
18
+ * `reportUnmatchedIgnoredErrors` zegt per entry wat er niet meer klopt; dat is dezelfde run
19
+ * als `unmatched_ignores`.
20
+ *
21
+ * Alles wat niet eenduidig terug te vinden is, blijft staan. Een fout in het koppelen laat
22
+ * dus hooguit een verouderde entry achter, en haalt nooit een entry weg die nog iets doet.
23
+ */
24
+ export async function pruneBaseline(project, options = {}, analyse = analyseUnmatched) {
25
+ const files = includedBaselineFiles(project);
26
+ // Zonder config includeert includedBaselineFiles niets; dit dekt dus ook dat geval.
27
+ if (project.config === null || files.length === 0) {
28
+ return nothingIncluded(project, noBaselineReason(project));
29
+ }
30
+ const before = new Map(files.map((file) => [file, readParsed(project, file)]));
31
+ // Eerst de entries voor een bestand dat er niet meer is. Met reportUnmatchedIgnoredErrors
32
+ // aan weigert PHPStan de héle run op zo'n pad ("is neither a directory, nor a file path"),
33
+ // dus dit moet vóór de analyse op schijf staan. Een bestand dat er niet is, kan geen fout
34
+ // hebben: weghalen verbergt niets.
35
+ const missing = new Map();
36
+ for (const file of files) {
37
+ const parsed = before.get(file);
38
+ const dead = parsed.blocks.filter((block) => block.known && block.path !== null && !GLOB.test(block.path) && !existsSync(resolve(project.path, dirname(file), block.path)));
39
+ if (dead.length > 0) {
40
+ writeBaseline(project, file, applyEdits(parsed, new Set(dead), new Map()));
41
+ missing.set(file, dead.length);
42
+ }
43
+ }
44
+ let result;
45
+ try {
46
+ result = await analyse(project, project.config, options);
47
+ }
48
+ catch (error) {
49
+ throw withMissingNotice(error, missing);
50
+ }
51
+ requireComplete(result, missing);
52
+ const rules = [];
53
+ let currentFindings = 0;
54
+ let inline = 0;
55
+ for (const finding of result.findings) {
56
+ // Een inline ignore die niets meer doet, staat in de code: dat is niet aan deze tool, en
57
+ // ook geen huidige bevinding die de baseline zou kunnen verstoppen.
58
+ if (readStaleInline(finding) !== null) {
59
+ inline++;
60
+ continue;
61
+ }
62
+ const rule = readStaleRule(finding, project.path);
63
+ // Een count die te laag is, is geen verouderde regel: de fout komt vaker voor, en die
64
+ // extra keer is een bevinding. Verhogen zou hem verstoppen.
65
+ if (rule === null || rule.kind === 'countTooLow') {
66
+ currentFindings++;
67
+ }
68
+ else {
69
+ rules.push(rule);
70
+ }
71
+ }
72
+ const general = result.generalErrors.filter((error) => unmatchedGeneralPattern(error) !== null).length;
73
+ const perFile = [];
74
+ const claimed = new Set();
75
+ let unknownCount = 0;
76
+ // Opnieuw inlezen: het bestand kan hierboven al gesnoeid zijn, en de regelnummers van
77
+ // toen kloppen dan niet meer.
78
+ const current = new Map(files.map((file) => [file, readParsed(project, file)]));
79
+ const edits = new Map(files.map((file) => [file, { remove: new Set(), lower: new Map() }]));
80
+ for (const rule of rules) {
81
+ const found = findBlock(project, current, rule, claimed);
82
+ if (found === null) {
83
+ continue;
84
+ }
85
+ claimed.add(found.block);
86
+ const edit = edits.get(found.file);
87
+ // Alleen omlaag, en alleen naar een getal dat PHPStan zelf noemde.
88
+ if (rule.kind === 'unmatched') {
89
+ edit.remove.add(found.block);
90
+ }
91
+ else if (rule.occurred === null || found.block.countLine === null || rule.occurred >= found.block.count) {
92
+ unknownCount++;
93
+ }
94
+ else {
95
+ edit.lower.set(found.block, rule.occurred);
96
+ }
97
+ }
98
+ for (const file of files) {
99
+ const parsed = current.get(file);
100
+ const edit = edits.get(file);
101
+ if (edit.remove.size > 0 || edit.lower.size > 0) {
102
+ writeBaseline(project, file, applyEdits(parsed, edit.remove, edit.lower));
103
+ }
104
+ const original = before.get(file);
105
+ const errorsAfter = errorsIn(parsed) - sumCounts(edit.remove) - [...edit.lower].reduce((sum, [block, count]) => sum + block.count - count, 0);
106
+ perFile.push({
107
+ file,
108
+ removed: edit.remove.size + (missing.get(file) ?? 0),
109
+ lowered: edit.lower.size,
110
+ errorsBefore: errorsIn(original),
111
+ errorsAfter,
112
+ });
113
+ }
114
+ return {
115
+ project: project.path,
116
+ files: perFile,
117
+ totals: {
118
+ removed: perFile.reduce((sum, file) => sum + file.removed, 0),
119
+ lowered: perFile.reduce((sum, file) => sum + file.lowered, 0),
120
+ missingFiles: [...missing.values()].reduce((sum, count) => sum + count, 0),
121
+ errorsBefore: perFile.reduce((sum, file) => sum + file.errorsBefore, 0),
122
+ errorsAfter: perFile.reduce((sum, file) => sum + file.errorsAfter, 0),
123
+ },
124
+ left: { outsideBaseline: rules.length - claimed.size, general, unknownCount, inline },
125
+ currentFindings,
126
+ notices: [],
127
+ };
128
+ }
129
+ /**
130
+ * Leest een baseline in blokken, met regelnummers, zodat een wijziging alleen die blokken
131
+ * raakt en de rest van het bestand byte voor byte blijft staan. Regelgericht, net als
132
+ * `parseBaseline`: PHPStan genereert dit bestand, dus de vorm ligt vast.
133
+ */
134
+ export function parseBaselineBlocks(content) {
135
+ const eol = content.includes('\r\n') ? '\r\n' : '\n';
136
+ const lines = content.split(/\r?\n/);
137
+ const listLine = lines.findIndex((line) => /^\s*ignoreErrors:\s*$/.test(line));
138
+ if (listLine === -1) {
139
+ return { lines, eol, listLine: null, blocks: [] };
140
+ }
141
+ const listIndent = indentOf(lines[listLine] ?? '');
142
+ const blocks = [];
143
+ let itemIndent = null;
144
+ let open = null;
145
+ let index = listLine + 1;
146
+ const close = (end) => {
147
+ if (open !== null) {
148
+ blocks.push(toBlock(open.start, end, open.known, open.fields));
149
+ }
150
+ open = null;
151
+ };
152
+ for (; index < lines.length; index++) {
153
+ const line = lines[index] ?? '';
154
+ const trimmed = line.trim();
155
+ if (trimmed === '') {
156
+ continue;
157
+ }
158
+ const indent = indentOf(line);
159
+ if (indent <= listIndent) {
160
+ break;
161
+ }
162
+ // Een nieuw lijstitem op dezelfde diepte als het eerste. `- message: …` op één regel
163
+ // maakt PHPStan niet; zo'n blok is een grens, maar wordt niet gelezen of aangeraakt.
164
+ if (trimmed.startsWith('-') && (itemIndent === null || indent === itemIndent)) {
165
+ itemIndent ??= indent;
166
+ close(index);
167
+ open = { start: index, known: trimmed === '-', fields: new Map() };
168
+ continue;
169
+ }
170
+ const field = /^(identifier|path|count|message|rawMessage):\s*(.*)$/.exec(trimmed);
171
+ if (open !== null && field?.[1] !== undefined) {
172
+ open.fields.set(field[1], { value: (field[2] ?? '').trim(), line: index });
173
+ }
174
+ }
175
+ close(index);
176
+ return { lines, eol, listLine, blocks };
177
+ }
178
+ /**
179
+ * Past de wijzigingen toe en geeft de nieuwe inhoud. Weghalen gaat per blok inclusief de
180
+ * lege regels erna; een lagere count vervangt alleen het getal op zijn eigen regel.
181
+ */
182
+ export function applyEdits(parsed, remove, lower) {
183
+ const lines = [...parsed.lines];
184
+ for (const [block, count] of lower) {
185
+ if (block.countLine !== null) {
186
+ lines[block.countLine] = (lines[block.countLine] ?? '').replace(/count:\s*\d+/, `count: ${String(count)}`);
187
+ }
188
+ }
189
+ const dropped = new Set();
190
+ for (const block of remove) {
191
+ for (let line = block.start; line < block.end; line++) {
192
+ dropped.add(line);
193
+ }
194
+ }
195
+ // De laatste regel is leeg als het bestand op een regeleinde eindigt; die hoort bij het
196
+ // bestand en niet bij het laatste blok.
197
+ const last = lines.length - 1;
198
+ if (lines[last] === '') {
199
+ dropped.delete(last);
200
+ }
201
+ const kept = lines.filter((_, index) => !dropped.has(index));
202
+ // Alles weg: `ignoreErrors:` zonder lijst eronder is in NEON null, en daar valt PHPStan
203
+ // over. Een lege lijst is wat er dan bedoeld wordt.
204
+ if (parsed.listLine !== null && remove.size === parsed.blocks.length) {
205
+ const at = kept.indexOf(parsed.lines[parsed.listLine] ?? '');
206
+ if (at !== -1) {
207
+ kept[at] = (kept[at] ?? '').replace(/ignoreErrors:\s*$/, 'ignoreErrors: []');
208
+ }
209
+ }
210
+ return kept.join(parsed.eol);
211
+ }
212
+ /** Schrijft een overzicht van wat er gedaan is, als tekst voor het model. */
213
+ export function renderPruned(result) {
214
+ const lines = [];
215
+ if (result.notices.length > 0) {
216
+ lines.push(...result.notices.map((notice) => `Note: ${notice}`), '');
217
+ }
218
+ const changed = result.files.filter((file) => file.removed > 0 || file.lowered > 0);
219
+ if (changed.length === 0) {
220
+ lines.push(result.files.length === 0
221
+ ? 'Nothing to prune.'
222
+ : `Nothing to prune: every entry in ${result.files.map((file) => file.file).join(', ')} still matches.`);
223
+ }
224
+ for (const file of changed) {
225
+ const delen = [`removed ${file.removed} ${file.removed === 1 ? 'entry' : 'entries'}`];
226
+ if (file.lowered > 0) {
227
+ delen.push(`lowered the count of ${file.lowered}`);
228
+ }
229
+ lines.push(`${file.file}: ${delen.join(', ')}; ${file.errorsBefore} → ${file.errorsAfter} ignored error(s).`);
230
+ }
231
+ if (result.totals.missingFiles > 0) {
232
+ lines.push(`${result.totals.missingFiles} of the removed entries named a file that no longer exists.`);
233
+ }
234
+ lines.push('', result.currentFindings > 0
235
+ ? `Nothing was added: the ${result.currentFindings} current finding(s) are still reported. Fix them at the ` +
236
+ 'source; generate_baseline would hide them in the baseline.'
237
+ : 'Nothing was added, and there are no current findings.');
238
+ const { left } = result;
239
+ if (left.outsideBaseline + left.general + left.unknownCount + left.inline > 0) {
240
+ lines.push('', 'Left alone:');
241
+ if (left.outsideBaseline > 0) {
242
+ lines.push(`- ${left.outsideBaseline} stale rule(s) with a path that are not in an included baseline: they are in ignoreErrors of the config. Edit that yourself.`);
243
+ }
244
+ if (left.general > 0) {
245
+ lines.push(`- ${left.general} pattern(s) without a path: from ignoreErrors of a config, often one a package ships. A baseline change does nothing about them.`);
246
+ }
247
+ if (left.unknownCount > 0) {
248
+ lines.push(`- ${left.unknownCount} count(s) that are too high, where PHPStan did not say how often the error still occurs.`);
249
+ }
250
+ if (left.inline > 0) {
251
+ lines.push(`- ${left.inline} inline @phpstan-ignore comment(s) that no longer suppress anything: those are in the code. unmatched_ignores lists them.`);
252
+ }
253
+ }
254
+ return lines.join('\n');
255
+ }
256
+ function toBlock(start, end, known, fields) {
257
+ const value = (key) => {
258
+ const field = fields.get(key);
259
+ return field === undefined ? null : unquote(field.value);
260
+ };
261
+ const message = value('message');
262
+ const raw = value('rawMessage');
263
+ const count = fields.get('count');
264
+ return {
265
+ start,
266
+ end,
267
+ known,
268
+ path: value('path'),
269
+ identifier: value('identifier'),
270
+ // Zoals PHPStans IgnoredError::stringifyPattern het schrijft: `rawMessage` tussen dubbele
271
+ // quotes, zodat hij te koppelen is aan de melding over een verouderde regel.
272
+ pattern: message ?? (raw === null ? null : `"${raw}"`),
273
+ count: count === undefined ? 1 : Number.parseInt(count.value, 10) || 1,
274
+ countLine: count?.line ?? null,
275
+ };
276
+ }
277
+ /**
278
+ * Het blok dat bij een verouderde regel hoort, of null als het niet eenduidig te vinden is.
279
+ * Een blok wordt maar één keer gebruikt: twee gelijke entries leveren twee meldingen op.
280
+ */
281
+ function findBlock(project, baselines, rule, claimed) {
282
+ for (const [file, parsed] of baselines) {
283
+ for (const block of parsed.blocks) {
284
+ if (!claimed.has(block) && matches(project, file, block, rule)) {
285
+ return { file, block };
286
+ }
287
+ }
288
+ }
289
+ return null;
290
+ }
291
+ function matches(project, file, block, rule) {
292
+ if (!block.known || block.path === null || !samePath(resolve(project.path, dirname(file), block.path), rule.file)) {
293
+ return false;
294
+ }
295
+ // Een regel met alleen een identifier noemt PHPStan bij die identifier, zonder haakjes.
296
+ if (block.pattern === null) {
297
+ return rule.identifier === null && rule.pattern === block.identifier;
298
+ }
299
+ return rule.pattern === block.pattern && rule.identifier === block.identifier;
300
+ }
301
+ function samePath(a, b) {
302
+ // Windows vergelijkt zonder hoofdletters, en PHPStan schrijft het pad zoals het die kreeg.
303
+ return process.platform === 'win32' ? normalize(a).toLowerCase() === normalize(b).toLowerCase() : normalize(a) === normalize(b);
304
+ }
305
+ /**
306
+ * Een onvolledige analyse laat entries verouderd lijken die dat niet zijn. Dan wordt er
307
+ * niets geschreven: liever niets snoeien dan een baseline half leeghalen omdat PHPStan
308
+ * halverwege klapte.
309
+ */
310
+ function requireComplete(result, missing) {
311
+ const incomplete = result.warnings.find((warning) => /Result is incomplete/i.test(warning));
312
+ const errors = result.generalErrors.filter((error) => unmatchedGeneralPattern(error) === null);
313
+ if (incomplete === undefined && errors.length === 0) {
314
+ return;
315
+ }
316
+ const reason = incomplete ?? errors.slice(0, 3).join('\n');
317
+ throw withMissingNotice(new PhpstanError(`The analysis did not cover the whole project, so the baseline was not pruned: an entry can look stale ` +
318
+ `when PHPStan simply did not get that far.\n\n${reason}`, 'phpstan_failed'), missing);
319
+ }
320
+ /** Een fout na het weghalen van entries voor verdwenen bestanden: dat is dan wél gebeurd. */
321
+ function withMissingNotice(error, missing) {
322
+ if (missing.size === 0 || !(error instanceof PhpstanError)) {
323
+ return error;
324
+ }
325
+ const wat = [...missing].map(([file, count]) => `${count} from ${file}`).join(', ');
326
+ return new PhpstanError(`${error.message}\n\nAlready removed before the analysis: the entries for files that no longer exist (${wat}).`, error.reason, error.details);
327
+ }
328
+ function readParsed(project, file) {
329
+ let content;
330
+ try {
331
+ content = readFileSync(join(project.path, file), 'utf8');
332
+ }
333
+ catch (error) {
334
+ throw new BaselineError(`${file} cannot be read: ${error instanceof Error ? error.message : String(error)}.`);
335
+ }
336
+ const parsed = parseBaselineBlocks(content);
337
+ // Hetzelfde onderscheid als bij baseline_summary: ignoreErrors noemen en niets opleveren is
338
+ // een vorm die we niet kennen, en daar wordt niet in geschreven.
339
+ if (parsed.blocks.filter((block) => block.known).length === 0 && parsed.blocks.length > 0) {
340
+ throw new BaselineError(`${file} contains ignoreErrors, but not a single entry could be read from it; it was not changed.`);
341
+ }
342
+ return parsed;
343
+ }
344
+ /**
345
+ * Via een tijdelijk bestand naast het origineel en een rename: een analyse die tegelijk
346
+ * leest, ziet het oude of het nieuwe bestand en nooit een half.
347
+ */
348
+ function writeBaseline(project, file, content) {
349
+ const target = join(project.path, file);
350
+ const tmp = `${target}.phpstan-mcp-${String(process.pid)}`;
351
+ try {
352
+ writeFileSync(tmp, content);
353
+ renameSync(tmp, target);
354
+ }
355
+ catch (error) {
356
+ throw new BaselineError(`${file} could not be written: ${error instanceof Error ? error.message : String(error)}.`);
357
+ }
358
+ }
359
+ function nothingIncluded(project, notice) {
360
+ return {
361
+ project: project.path,
362
+ files: [],
363
+ totals: { removed: 0, lowered: 0, missingFiles: 0, errorsBefore: 0, errorsAfter: 0 },
364
+ left: { outsideBaseline: 0, general: 0, unknownCount: 0, inline: 0 },
365
+ currentFindings: 0,
366
+ notices: [notice],
367
+ };
368
+ }
369
+ function noBaselineReason(project) {
370
+ return project.config === null
371
+ ? `${project.path} has no phpstan.neon(.dist), so no baseline is included.`
372
+ : `The config of ${project.path} includes no phpstan-baseline*.neon from the project root; there is nothing to prune.`;
373
+ }
374
+ function errorsIn(parsed) {
375
+ return parsed.blocks.reduce((sum, block) => sum + block.count, 0);
376
+ }
377
+ function sumCounts(blocks) {
378
+ let sum = 0;
379
+ for (const block of blocks) {
380
+ sum += block.count;
381
+ }
382
+ return sum;
383
+ }
384
+ function indentOf(line) {
385
+ return /^\s*/.exec(line)?.[0].length ?? 0;
386
+ }
387
+ //# sourceMappingURL=prune.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prune.js","sourceRoot":"","sources":["../src/prune.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC9E,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE9E,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAyD3G,2FAA2F;AAC3F,MAAM,IAAI,GAAG,SAAS,CAAC;AASvB;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,OAAwB,EACxB,UAAwE,EAAE,EAC1E,UAA6B,gBAAgB;IAG7C,MAAM,KAAK,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAE7C,oFAAoF;IACpF,IAAI,OAAO,CAAC,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClD,OAAO,eAAe,CAAC,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAE/E,0FAA0F;IAC1F,2FAA2F;IAC3F,0FAA0F;IAC1F,mCAAmC;IACnC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IAE1C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAmB,CAAC;QAClD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAE5K,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;YAC3E,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,IAAI,MAAsB,CAAC;IAE3B,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,iBAAiB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEjC,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,IAAI,eAAe,GAAG,CAAC,CAAC;IACxB,IAAI,MAAM,GAAG,CAAC,CAAC;IAEf,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QAEtC,yFAAyF;QACzF,oEAAoE;QACpE,IAAI,eAAe,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;YACtC,MAAM,EAAE,CAAC;YACT,SAAS;QACX,CAAC;QAED,MAAM,IAAI,GAAG,aAAa,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QAElD,sFAAsF;QACtF,4DAA4D;QAC5D,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YACjD,eAAe,EAAE,CAAC;QACpB,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;IACvG,MAAM,OAAO,GAAmC,EAAE,CAAC;IACnD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAC;IACzC,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,sFAAsF;IACtF,8BAA8B;IAC9B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAChF,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,GAAG,EAAiB,EAAE,KAAK,EAAE,IAAI,GAAG,EAAyB,EAAE,CAAC,CAAC,CAAC,CAAC;IAElI,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QAEzD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACnB,SAAS;QACX,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACzB,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAsE,CAAC;QAExG,mEAAmE;QACnE,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAC9B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;aAAM,IAAI,IAAI,CAAC,QAAQ,KAAK,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,SAAS,KAAK,IAAI,IAAI,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YAC1G,YAAY,EAAE,CAAC;QACjB,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAmB,CAAC;QACnD,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAsE,CAAC;QAElG,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAChD,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5E,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAmB,CAAC;QACpD,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC;QAC9I,OAAO,CAAC,IAAI,CAAC;YACX,IAAI;YACJ,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpD,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI;YACxB,YAAY,EAAE,QAAQ,CAAC,QAAQ,CAAC;YAChC,WAAW;SACZ,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,IAAI;QACrB,KAAK,EAAE,OAAO;QACd,MAAM,EAAE;YACN,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7D,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7D,YAAY,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC;YAC1E,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;YACvE,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;SACtE;QACD,IAAI,EAAE,EAAE,eAAe,EAAE,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE;QACrF,eAAe;QACf,OAAO,EAAE,EAAE;KACZ,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe;IAEjD,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAE/E,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;QACpB,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACpD,CAAC;IAED,MAAM,UAAU,GAAG,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACnD,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,IAAI,UAAU,GAAkB,IAAI,CAAC;IACrC,IAAI,IAAI,GAAmG,IAAI,CAAC;IAChH,IAAI,KAAK,GAAG,QAAQ,GAAG,CAAC,CAAC;IAEzB,MAAM,KAAK,GAAG,CAAC,GAAW,EAAQ,EAAE;QAClC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAClB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACjE,CAAC;QAED,IAAI,GAAG,IAAI,CAAC;IACd,CAAC,CAAC;IAEF,OAAO,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAE5B,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;YACnB,SAAS;QACX,CAAC;QAED,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;QAE9B,IAAI,MAAM,IAAI,UAAU,EAAE,CAAC;YACzB,MAAM;QACR,CAAC;QAED,qFAAqF;QACrF,qFAAqF;QACrF,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,KAAK,IAAI,IAAI,MAAM,KAAK,UAAU,CAAC,EAAE,CAAC;YAC9E,UAAU,KAAK,MAAM,CAAC;YACtB,KAAK,CAAC,KAAK,CAAC,CAAC;YACb,IAAI,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,KAAK,GAAG,EAAE,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,CAAC;YACnE,SAAS;QACX,CAAC;QAED,MAAM,KAAK,GAAG,sDAAsD,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAEnF,IAAI,IAAI,KAAK,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;YAC7C,IAAiE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3I,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,CAAC;IACb,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,MAAsB,EAAE,MAAkC,EAAE,KAAyC;IAE9H,MAAM,KAAK,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAEhC,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,KAAK,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;YAC7B,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,UAAU,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC7G,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAElC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,KAAK,IAAI,IAAI,GAAG,KAAK,CAAC,KAAK,EAAE,IAAI,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC;YACtD,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED,wFAAwF;IACxF,wCAAwC;IACxC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAE9B,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;QACvB,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;IAE7D,wFAAwF;IACxF,oDAAoD;IACpD,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACrE,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;QAE7D,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;YACd,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,mBAAmB,EAAE,kBAAkB,CAAC,CAAC;QAC/E,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC/B,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,YAAY,CAAC,MAAmB;IAE9C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;IAEpF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CACR,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YACvB,CAAC,CAAC,mBAAmB;YACrB,CAAC,CAAC,oCAAoC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAC1G,CAAC;IACJ,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,MAAM,KAAK,GAAG,CAAC,WAAW,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC;QAEtF,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC,wBAAwB,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QACrD,CAAC;QAED,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,YAAY,MAAM,IAAI,CAAC,WAAW,oBAAoB,CAAC,CAAC;IAChH,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;QACnC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,6DAA6D,CAAC,CAAC;IACzG,CAAC;IAED,KAAK,CAAC,IAAI,CACR,EAAE,EACF,MAAM,CAAC,eAAe,GAAG,CAAC;QACxB,CAAC,CAAC,0BAA0B,MAAM,CAAC,eAAe,0DAA0D;YACxG,4DAA4D;QAChE,CAAC,CAAC,uDAAuD,CAC5D,CAAC;IAEF,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC;IAExB,IAAI,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9E,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,aAAa,CAAC,CAAC;QAE9B,IAAI,IAAI,CAAC,eAAe,GAAG,CAAC,EAAE,CAAC;YAC7B,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,eAAe,8HAA8H,CAAC,CAAC;QACtK,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,GAAG,CAAC,EAAE,CAAC;YACrB,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO,kIAAkI,CAAC,CAAC;QAClK,CAAC;QAED,IAAI,IAAI,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;YAC1B,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,YAAY,0FAA0F,CAAC,CAAC;QAC/H,CAAC;QAED,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM,2HAA2H,CAAC,CAAC;QAC1J,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,OAAO,CAAC,KAAa,EAAE,GAAW,EAAE,KAAc,EAAE,MAAoD;IAE/G,MAAM,KAAK,GAAG,CAAC,GAAW,EAAE,EAAE;QAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC9B,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC3D,CAAC,CAAC;IACF,MAAM,OAAO,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IACjC,MAAM,GAAG,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAElC,OAAO;QACL,KAAK;QACL,GAAG;QACH,KAAK;QACL,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC;QACnB,UAAU,EAAE,KAAK,CAAC,YAAY,CAAC;QAC/B,0FAA0F;QAC1F,6EAA6E;QAC7E,OAAO,EAAE,OAAO,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,CAAC;QACtD,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC;QACtE,SAAS,EAAE,KAAK,EAAE,IAAI,IAAI,IAAI;KAC/B,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,SAAS,CAChB,OAAwB,EACxB,SAA8C,EAC9C,IAAe,EACf,OAAmC;IAGnC,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;QACvC,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC;gBAC/D,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;YACzB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,OAAO,CAAC,OAAwB,EAAE,IAAY,EAAE,KAAoB,EAAE,IAAe;IAE5F,IAAI,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAClH,OAAO,KAAK,CAAC;IACf,CAAC;IAED,wFAAwF;IACxF,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC,UAAU,KAAK,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,UAAU,CAAC;IACvE,CAAC;IAED,OAAO,IAAI,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC,UAAU,KAAK,KAAK,CAAC,UAAU,CAAC;AAChF,CAAC;AAED,SAAS,QAAQ,CAAC,CAAS,EAAE,CAAS;IAEpC,2FAA2F;IAC3F,OAAO,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC;AAClI,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe,CAAC,MAAsB,EAAE,OAAoC;IAEnF,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,uBAAuB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAC5F,MAAM,MAAM,GAAG,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,uBAAuB,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;IAE/F,IAAI,UAAU,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpD,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3D,MAAM,iBAAiB,CACrB,IAAI,YAAY,CACd,wGAAwG;QACtG,gDAAgD,MAAM,EAAE,EAC1D,gBAAgB,CACjB,EACD,OAAO,CACR,CAAC;AACJ,CAAC;AAED,6FAA6F;AAC7F,SAAS,iBAAiB,CAAC,KAAc,EAAE,OAAoC;IAE7E,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,YAAY,YAAY,CAAC,EAAE,CAAC;QAC3D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,GAAG,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,SAAS,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpF,OAAO,IAAI,YAAY,CACrB,GAAG,KAAK,CAAC,OAAO,wFAAwF,GAAG,IAAI,EAC/G,KAAK,CAAC,MAAM,EACZ,KAAK,CAAC,OAAO,CACd,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,OAAwB,EAAE,IAAY;IAExD,IAAI,OAAe,CAAC;IAEpB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IAC3D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,aAAa,CAAC,GAAG,IAAI,oBAAoB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChH,CAAC;IAED,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAE5C,4FAA4F;IAC5F,iEAAiE;IACjE,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1F,MAAM,IAAI,aAAa,CAAC,GAAG,IAAI,2FAA2F,CAAC,CAAC;IAC9H,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,SAAS,aAAa,CAAC,OAAwB,EAAE,IAAY,EAAE,OAAe;IAE5E,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,GAAG,MAAM,gBAAgB,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;IAE3D,IAAI,CAAC;QACH,aAAa,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC5B,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,aAAa,CAAC,GAAG,IAAI,0BAA0B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACtH,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,OAAwB,EAAE,MAAc;IAE/D,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,IAAI;QACrB,KAAK,EAAE,EAAE;QACT,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE;QACpF,IAAI,EAAE,EAAE,eAAe,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;QACpE,eAAe,EAAE,CAAC;QAClB,OAAO,EAAE,CAAC,MAAM,CAAC;KAClB,CAAC;AACJ,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAwB;IAEhD,OAAO,OAAO,CAAC,MAAM,KAAK,IAAI;QAC5B,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,0DAA0D;QAC3E,CAAC,CAAC,iBAAiB,OAAO,CAAC,IAAI,uFAAuF,CAAC;AAC3H,CAAC;AAED,SAAS,QAAQ,CAAC,MAAsB;IAEtC,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,SAAS,CAAC,MAA+B;IAEhD,IAAI,GAAG,GAAG,CAAC,CAAC;IAEZ,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,GAAG,IAAI,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAE5B,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC;AAC5C,CAAC"}
@@ -12,6 +12,8 @@ export declare function registerServerConfig(server: McpServer, roots: RootSourc
12
12
  export declare function registerDiagnose(server: McpServer, roots: RootSource): void;
13
13
  /** Hangt `baseline_summary` aan de server: wat de baseline verbergt. */
14
14
  export declare function registerBaselineSummary(server: McpServer, roots: RootSource): void;
15
+ /** Hangt `baseline_history` aan de server: hoe de baseline door de git-geschiedenis veranderde. */
16
+ export declare function registerBaselineHistory(server: McpServer, roots: RootSource): void;
15
17
  /** Hangt `unmatched_ignores` aan de server: negeerregels die niets meer doen. */
16
18
  export declare function registerUnmatchedIgnores(server: McpServer, roots: RootSource): void;
17
19
  /** Hangt `inspect_type` aan de server: het type dat PHPStan op een regel afleidt. */
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { baselineEntriesFor, renderBaseline, summariseBaseline } from '../baseline.js';
3
3
  import { GitError, countInlineIgnores } from '../changed.js';
4
+ import { DEFAULT_HISTORY_LIMIT, MAX_HISTORY_LIMIT, baselineHistory, renderHistory } from '../history.js';
4
5
  import { diagnose, loadDefaultMemoryLimit, loadMaxParallel, serverInvocation } from '../phpstan.js';
5
6
  import { findMissingExtensions, readComposerScripts, renderComposerScripts, renderMissingExtensions } from '../composer.js';
6
7
  import { discoverProjects } from '../project.js';
@@ -447,7 +448,8 @@ function renderInlineIgnores({ inlineIgnores: inline, inlineIgnoresProblem: prob
447
448
  }
448
449
  const lines = [
449
450
  `${String(inline.total)} inline @phpstan-ignore comment(s) in ${String(inline.files)} file(s) counted in all PHP ` +
450
- 'files git knows, also outside the analysed paths. These are hidden too, and a new baseline does not touch them.',
451
+ 'files git knows, also outside the analysed paths. These are hidden too, and a new baseline does not touch them. ' +
452
+ 'unmatched_ignores says which of them no longer suppress anything.',
451
453
  ];
452
454
  if (inline.withoutIdentifier > 0) {
453
455
  lines.push(`${String(inline.withoutIdentifier)} have no identifier (@phpstan-ignore-line, -next-line): they ignore every error on their line.`);
@@ -474,13 +476,71 @@ function renderEntries(path, entries) {
474
476
  ...entries.map((entry) => ` ${String(entry.count).padStart(3)} ${entry.identifier}${kort(entry.message)}`),
475
477
  ].join('\n');
476
478
  }
479
+ /** Hangt `baseline_history` aan de server: hoe de baseline door de git-geschiedenis veranderde. */
480
+ export function registerBaselineHistory(server, roots) {
481
+ const count = z.object({ entries: z.number(), errors: z.number().describe('Ignored errors; one entry can cover several') });
482
+ server.registerTool('baseline_history', {
483
+ title: 'Baseline history',
484
+ description: 'Shows how the included phpstan-baseline*.neon changed through the git history: per commit that touched ' +
485
+ 'it, the number of ignored errors, the change against the previous version, and which identifiers grew or ' +
486
+ 'shrank most. Answers whether a baseline is being paid off or is quietly growing, and which commit added a ' +
487
+ 'pile. Follows the first-parent line of HEAD, so a merged branch is one change. Reads git only; does not ' +
488
+ 'run PHPStan.',
489
+ inputSchema: {
490
+ project: projectArgument,
491
+ limit: z
492
+ .number()
493
+ .int()
494
+ .min(1)
495
+ .max(MAX_HISTORY_LIMIT)
496
+ .optional()
497
+ .describe(`How many changes per baseline file (default ${String(DEFAULT_HISTORY_LIMIT)})`),
498
+ },
499
+ outputSchema: {
500
+ project: z.string(),
501
+ files: z.array(z.object({
502
+ file: z.string(),
503
+ rows: z
504
+ .array(z.object({
505
+ commit: nullable(z.string()).describe('Null for the working copy, when it differs from the last commit'),
506
+ date: nullable(z.string()).describe('Commit date, ISO 8601'),
507
+ subject: nullable(z.string()),
508
+ file: z.string().describe('The name of the baseline in that commit; differs when it was renamed later'),
509
+ count: nullable(count).describe('Null if the baseline could not be read in that commit'),
510
+ delta: nullable(count).describe('Against the row below; null if either could not be read'),
511
+ changes: z
512
+ .array(z.object({ identifier: z.string(), change: z.number() }))
513
+ .describe('The identifiers that changed most, in ignored errors; positive means added'),
514
+ }))
515
+ .describe('Newest first'),
516
+ start: nullable(count).describe('The baseline before the oldest row; zero when it was created there'),
517
+ olderCommits: z.boolean().describe('Whether older commits changed it too; a higher limit shows them'),
518
+ })),
519
+ notices: z.array(z.string()),
520
+ },
521
+ annotations: readOnly(),
522
+ }, async ({ project, limit }) => {
523
+ try {
524
+ const resolved = await roots.resolveProject(project);
525
+ const history = await baselineHistory(resolved, limit);
526
+ return {
527
+ content: [{ type: 'text', text: renderHistory(history) }],
528
+ structuredContent: { ...history },
529
+ };
530
+ }
531
+ catch (error) {
532
+ return toFailure(error);
533
+ }
534
+ });
535
+ }
477
536
  /** Hangt `unmatched_ignores` aan de server: negeerregels die niets meer doen. */
478
537
  export function registerUnmatchedIgnores(server, roots) {
479
538
  server.registerTool('unmatched_ignores', {
480
539
  title: 'Find stale ignore rules',
481
540
  description: 'Runs a full analysis with reportUnmatchedIgnoredErrors enabled and returns the ' +
482
541
  'ignore rules that no longer match anything, or match less often than their count ' +
483
- 'from the baseline and from ignoreErrors in the config. Such a rule belongs to an ' +
542
+ 'from the baseline and from ignoreErrors in the config, plus the inline @phpstan-ignore ' +
543
+ 'comments that no longer suppress anything. Such a rule belongs to an ' +
484
544
  'error that has been fixed and can be removed. Does not change anything in the ' +
485
545
  'project: the temporary config lives outside it for a project with a runner, in ' +
486
546
  "that runner's temp directory.",
@@ -508,6 +568,8 @@ export function registerUnmatchedIgnores(server, roots) {
508
568
  countTooHigh: z.number(),
509
569
  inBaseline: z.number(),
510
570
  general: z.number(),
571
+ inline: z.number().describe('Inline @phpstan-ignore comments in the code that no longer suppress anything'),
572
+ inlineWithoutIdentifier: z.number().describe('How many of those name no identifier: they ignore every error on their line'),
511
573
  otherFindings: z
512
574
  .number()
513
575
  .describe('Regular findings from the same run; a new baseline would hide those too'),
@@ -525,12 +587,19 @@ export function registerUnmatchedIgnores(server, roots) {
525
587
  general: z
526
588
  .array(z.string())
527
589
  .describe('Patterns without a path that no longer match anything; these are not in the baseline'),
590
+ inline: z
591
+ .array(z.object({
592
+ path: z.string().describe('Path relative to the project root'),
593
+ line: nullable(z.number()).describe('The line the ignore applies to; for a comment on a line of its own, the line below it'),
594
+ identifier: nullable(z.string()).describe('The identifier that no longer occurs there; null for an ignore without identifier'),
595
+ }))
596
+ .describe('Inline @phpstan-ignore comments that no longer suppress anything; these are in the code, not in a baseline'),
528
597
  generalErrors: z.array(z.string()),
529
598
  warnings: z
530
599
  .array(z.string())
531
600
  .describe('If this contains "Result is incomplete", PHPStan may have missed stale rules'),
532
601
  notices: z.array(z.string()),
533
- omitted: z.object({ general: z.number(), generalErrors: z.number(), warnings: z.number() }),
602
+ omitted: z.object({ general: z.number(), inline: z.number(), generalErrors: z.number(), warnings: z.number() }),
534
603
  page: z.object({
535
604
  offset: z.number(),
536
605
  limit: z.number(),
@@ -555,6 +624,7 @@ export function registerUnmatchedIgnores(server, roots) {
555
624
  project: resolved.path,
556
625
  stale: report.totals.unmatched + report.totals.countTooHigh,
557
626
  general: report.totals.general,
627
+ inline: report.totals.inline,
558
628
  durationMs: Date.now() - begin,
559
629
  });
560
630
  return {