Skip to content

Commit 92b9b7f

Browse files
Merge pull request #135 from MehediDracula/claude/perf-diagnostics-on-save-4sKT7
Run diagnostics on save instead of every keystroke
2 parents e0ffb02 + 8d407f6 commit 92b9b7f

4 files changed

Lines changed: 47 additions & 107 deletions

File tree

src/core/PhpClassDetector.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,8 @@ export class PhpClassDetector {
375375
let trimmed = part.trim();
376376
if (!trimmed) { continue; }
377377

378-
// Strip PHP 8 attributes (e.g. #[CurrentUser])
379-
trimmed = trimmed.replace(/#\[[^\]]*\]\s*/g, '');
378+
// Strip PHP 8 attributes (e.g. #[CurrentUser], #[MapQueryString(validationGroups: ["strict"])])
379+
trimmed = trimmed.replace(/#\[(?:[^\[\]]|\[(?:[^\[\]]|\[[^\[\]]*\])*\])*\]\s*/g, '');
380380

381381
// Strip constructor promotion modifiers
382382
trimmed = trimmed.replace(/^(?:public|protected|private)\s+/, '');
@@ -401,13 +401,14 @@ function matchAll(text: string, regex: RegExp): string[] {
401401
return results;
402402
}
403403

404+
const SCALAR_TYPES = new Set([
405+
'String', 'Int', 'Float', 'Bool', 'Array', 'Object',
406+
'Null', 'Void', 'Never', 'Mixed', 'Self', 'Static', 'Parent',
407+
'True', 'False', 'Iterable', 'Callable',
408+
]);
409+
404410
function isScalarType(name: string): boolean {
405-
const scalars = new Set([
406-
'String', 'Int', 'Float', 'Bool', 'Array', 'Object',
407-
'Null', 'Void', 'Never', 'Mixed', 'Self', 'Static', 'Parent',
408-
'True', 'False', 'Iterable', 'Callable',
409-
]);
410-
return scalars.has(name);
411+
return SCALAR_TYPES.has(name);
411412
}
412413

413414
function escapeRegex(str: string): string {

src/extension.ts

Lines changed: 34 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -95,49 +95,42 @@ export function activate(context: vscode.ExtensionContext): void {
9595
const needsSort = getConfig('sortOnSave');
9696
if (!needsRemove && !needsSort) { return; }
9797

98-
diagnosticManager.suppressUpdates = true;
99-
10098
event.waitUntil(((): Promise<vscode.TextEdit[]> => {
101-
try {
102-
const document = event.document;
103-
const { useStatements } = parser.parse(document);
104-
if (useStatements.length === 0) { return Promise.resolve([]); }
105-
106-
let stmtsToKeep = useStatements;
107-
108-
// Filter out unused class imports
109-
if (needsRemove) {
110-
const text = document.getText();
111-
const detectedClasses = detector.detectAll(text);
112-
const ignoreList = getConfig('ignoreList');
113-
stmtsToKeep = useStatements.filter(stmt => {
114-
if (stmt.kind !== 'class') { return true; }
115-
if (ignoreList.includes(stmt.className)) { return true; }
116-
if (detectedClasses.includes(stmt.className)) { return true; }
117-
if (text.includes(`${stmt.className}\\`)) { return true; }
118-
return false;
119-
});
120-
}
121-
122-
// Sort remaining imports (or just sort without remove)
123-
if (needsSort && stmtsToKeep.length > 1) {
124-
return Promise.resolve(sortManager.computeSortEdits(document, stmtsToKeep));
125-
}
126-
127-
// Remove-only: delete unused lines (reverse order for stable line numbers)
128-
if (needsRemove && stmtsToKeep.length < useStatements.length) {
129-
const removed = useStatements.filter(s => !stmtsToKeep.includes(s));
130-
const edits = removed
131-
.sort((a, b) => b.line - a.line)
132-
.map(stmt => vscode.TextEdit.delete(document.lineAt(stmt.line).rangeIncludingLineBreak));
133-
return Promise.resolve(edits);
134-
}
135-
136-
return Promise.resolve([]);
137-
} finally {
138-
// Defer re-enabling so diagnostics don't fire during the save edit application
139-
setTimeout(() => { diagnosticManager.suppressUpdates = false; }, 100);
99+
const document = event.document;
100+
const { useStatements } = parser.parse(document);
101+
if (useStatements.length === 0) { return Promise.resolve([]); }
102+
103+
let stmtsToKeep = useStatements;
104+
105+
// Filter out unused class imports
106+
if (needsRemove) {
107+
const text = document.getText();
108+
const detectedClasses = detector.detectAll(text);
109+
const ignoreList = getConfig('ignoreList');
110+
stmtsToKeep = useStatements.filter(stmt => {
111+
if (stmt.kind !== 'class') { return true; }
112+
if (ignoreList.includes(stmt.className)) { return true; }
113+
if (detectedClasses.includes(stmt.className)) { return true; }
114+
if (text.includes(`${stmt.className}\\`)) { return true; }
115+
return false;
116+
});
117+
}
118+
119+
// Sort remaining imports (or just sort without remove)
120+
if (needsSort && stmtsToKeep.length > 1) {
121+
return Promise.resolve(sortManager.computeSortEdits(document, stmtsToKeep));
140122
}
123+
124+
// Remove-only: delete unused lines (reverse order for stable line numbers)
125+
if (needsRemove && stmtsToKeep.length < useStatements.length) {
126+
const removed = useStatements.filter(s => !stmtsToKeep.includes(s));
127+
const edits = removed
128+
.sort((a, b) => b.line - a.line)
129+
.map(stmt => vscode.TextEdit.delete(document.lineAt(stmt.line).rangeIncludingLineBreak));
130+
return Promise.resolve(edits);
131+
}
132+
133+
return Promise.resolve([]);
141134
})());
142135
})
143136
);

src/features/DiagnosticManager.ts

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,8 @@ const DIAGNOSTIC_SOURCE = 'PHP Namespace Resolver';
1111
export class DiagnosticManager implements vscode.Disposable {
1212
private collection: vscode.DiagnosticCollection;
1313
private disposables: vscode.Disposable[] = [];
14-
private debounceTimer: ReturnType<typeof setTimeout> | undefined;
15-
private _suppressUpdates = false;
1614
private lastVersion = new Map<string, number>();
1715

18-
/** Suppress diagnostic updates during programmatic edits (e.g. save operations). */
19-
set suppressUpdates(value: boolean) { this._suppressUpdates = value; }
20-
2116
constructor(
2217
private detector: PhpClassDetector,
2318
private parser: DeclarationParser,
@@ -27,16 +22,9 @@ export class DiagnosticManager implements vscode.Disposable {
2722

2823
this.disposables.push(
2924
cache.onDidFinishIndexing(() => this.refreshVisible()),
30-
vscode.window.onDidChangeActiveTextEditor(editor => {
31-
if (!editor || editor.document.languageId !== 'php') { return; }
32-
this.update(editor.document);
33-
}),
34-
vscode.workspace.onDidChangeTextDocument(event => {
35-
if (event.document.languageId !== 'php') { return; }
36-
if (this._suppressUpdates) { return; }
37-
clearTimeout(this.debounceTimer);
38-
const doc = event.document;
39-
this.debounceTimer = setTimeout(() => this.update(doc), 800);
25+
vscode.workspace.onDidSaveTextDocument(document => {
26+
if (document.languageId !== 'php') { return; }
27+
this.update(document);
4028
}),
4129
vscode.workspace.onDidCloseTextDocument(document => {
4230
this.clear(document.uri);
@@ -101,7 +89,6 @@ export class DiagnosticManager implements vscode.Disposable {
10189
}
10290

10391
dispose(): void {
104-
clearTimeout(this.debounceTimer);
10592
this.disposables.forEach(d => d.dispose());
10693
this.collection.dispose();
10794
}

0 commit comments

Comments
 (0)