Skip to content

Commit

Permalink
Add PHPStan Pro support (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
SanderRonde committed Nov 15, 2023
1 parent 53bba94 commit ce2756d
Show file tree
Hide file tree
Showing 24 changed files with 733 additions and 190 deletions.
13 changes: 10 additions & 3 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { DocumentManager } from './lib/documentManager';
import { registerListeners } from './lib/commands';
import { ErrorManager } from './lib/errorManager';
import type { ExtensionContext } from 'vscode';
import { PHPStanProManager } from './lib/pro';
import { StatusBar } from './lib/statusBar';
import { ProcessSpawner } from './lib/proc';
import { window, workspace } from 'vscode';
Expand All @@ -39,7 +40,6 @@ async function startLanguageServer(
run: {
module: serverModule,
transport: TransportKind.ipc,

},
debug: {
module: serverModule,
Expand Down Expand Up @@ -85,11 +85,18 @@ export async function activate(context: ExtensionContext): Promise<void> {
const watcher = new DocumentManager(client);
const errorManager = new ErrorManager(client);
const procSpawner = new ProcessSpawner(client, context);
const proManager = new PHPStanProManager(client);

registerListeners(context, client, errorManager);
registerListeners(context, client, errorManager, proManager);
registerConfigListeners();
registerLogMessager(context, client);
context.subscriptions.push(statusBar, watcher, errorManager, procSpawner);
context.subscriptions.push(
statusBar,
watcher,
errorManager,
procSpawner,
proManager
);

let wasReady = false;
context.subscriptions.push(
Expand Down
24 changes: 23 additions & 1 deletion client/src/lib/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import { commands, Commands } from '../../../shared/commands/defs';
import { autoRegisterCommand } from 'vscode-generate-package-json';
import type { LanguageClient } from 'vscode-languageclient/node';
import type { ErrorManager } from './errorManager';
import type { PHPStanProManager } from './pro';
import { showError } from './errorUtil';
import * as vscode from 'vscode';

export function registerListeners(
context: vscode.ExtensionContext,
client: LanguageClient,
errorManager: ErrorManager
errorManager: ErrorManager,
phpstanProManager: PHPStanProManager
): void {
context.subscriptions.push(
autoRegisterCommand(
Expand Down Expand Up @@ -47,6 +49,26 @@ export function registerListeners(
)
);

context.subscriptions.push(
autoRegisterCommand(
Commands.OPEN_PHPSTAN_PRO,
() => {
if (!phpstanProManager.port) {
void vscode.window.showErrorMessage(
'PHPStan Pro is not running'
);
return;
}
void vscode.env.openExternal(
vscode.Uri.parse(
`http://127.0.0.1:${phpstanProManager.port}`
)
);
},
commands
)
);

context.subscriptions.push(
autoRegisterCommand(
Commands.RELOAD,
Expand Down
8 changes: 8 additions & 0 deletions client/src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// eslint-disable-next-line node/no-extraneous-import
import type { TypedWorkspaceConfiguration } from 'vscode-generate-package-json';

import type { ConfigSettings } from '../../../shared/config';
import { CONFIG_KEYS } from '../../../shared/config';
import { window, workspace } from 'vscode';
Expand Down Expand Up @@ -36,6 +37,13 @@ export function registerConfigListeners(): void {
'On-hover type information is disabled when the paths setting is being used'
);
}
} else if (
e.affectsConfiguration('phpstan.pro') ||
e.affectsConfiguration('phpstan.proTmpDir')
) {
await window.showInformationMessage(
'Please reload your editor for changes to the PHPStan Pro configuration to take effect'
);
}
});
}
77 changes: 67 additions & 10 deletions client/src/lib/errorManager.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,90 @@
import type { PHPStanError } from '../../../shared/notificationChannels';
import type { LanguageClient } from 'vscode-languageclient/node';
import { errorNotification } from './notificationChannels';
import type { Disposable } from 'vscode';
import * as vscode from 'vscode';

interface PHPStanError {
message: string;
lineNumber: number;
}

export class ErrorManager implements Disposable, vscode.CodeActionProvider {
private readonly _diagnosticsCollection: vscode.DiagnosticCollection;
private readonly _errors: Map<string, PHPStanError[]> = new Map();
private _errors: {
fileSpecificErrors: Map<
string,
{
message: string;
lineNumber: number;
}[]
>;
notFileSpecificErrors: string[];
} = {
fileSpecificErrors: new Map(),
notFileSpecificErrors: [],
};
private _disposables: Disposable[] = [];

public constructor(client: LanguageClient) {
this._disposables.push(
client.onNotification(errorNotification, (params) => {
this._errors.clear();
this._errors = {
fileSpecificErrors: new Map(),
notFileSpecificErrors: [],
};
this._diagnosticsCollection.clear();
for (const uri in params.diagnostics) {
this._errors.set(uri, params.diagnostics[uri]);
this._showErrors(uri, params.diagnostics[uri]);
for (const uri in params.diagnostics.fileSpecificErrors) {
this._errors.fileSpecificErrors.set(
uri,
params.diagnostics.fileSpecificErrors[uri]
);
this._showErrors(
uri,
params.diagnostics.fileSpecificErrors[uri]
);
}
})
);
this._diagnosticsCollection =
vscode.languages.createDiagnosticCollection('PHPStan');
this._disposables.push(this._diagnosticsCollection);

let lastEditor: vscode.TextEditor | undefined = undefined;
this._disposables.push(
vscode.window.onDidChangeActiveTextEditor((editor) => {
if (lastEditor) {
this._showErrors(
lastEditor.document.fileName,
this._errors.fileSpecificErrors.get(
lastEditor.document.fileName
) ?? []
);
}

if (editor) {
this._showErrors(editor.document.fileName, [
...(this._errors.fileSpecificErrors.get(
editor.document.fileName
) ?? []),
...this._errors.notFileSpecificErrors.map(
(message) => ({
lineNumber: 0,
message,
})
),
]);
}
lastEditor = editor;
})
);
this._disposables.push(
vscode.workspace.onDidOpenTextDocument((e) => {
if (this._errors.has(e.fileName)) {
if (this._errors.fileSpecificErrors.has(e.fileName)) {
// Refresh, we might have some info on the chars
this._showErrors(e.fileName, this._errors.get(e.fileName)!);
this._showErrors(
e.fileName,
this._errors.fileSpecificErrors.get(e.fileName)!
);
}
})
);
Expand Down Expand Up @@ -170,11 +227,11 @@ export class ErrorManager implements Disposable, vscode.CodeActionProvider {
range: vscode.Range | vscode.Selection
): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> {
const uri = document.uri.toString();
if (!this._errors.has(uri)) {
if (!this._errors.fileSpecificErrors.has(uri)) {
return [];
}

const errors = this._errors.get(uri)!;
const errors = this._errors.fileSpecificErrors.get(uri)!;

const actions: ErrorCodeAction[] = [];

Expand Down
6 changes: 6 additions & 0 deletions client/src/lib/notificationChannels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {
CommandNotificationType,
ErrorNotificationType,
LogNotificationType,
PHPStanProNotificationType,
ProcessNotificationType,
ReadyNotificationType,
StatusBarNotificationType,
Expand Down Expand Up @@ -35,3 +36,8 @@ export const errorNotification = new NotificationType<ErrorNotificationType>(

export const processNotification =
new NotificationType<ProcessNotificationType>(NotificationChannel.SPAWNER);

export const phpstanProNotification =
new NotificationType<PHPStanProNotificationType>(
NotificationChannel.PHPSTAN_PRO
);
43 changes: 43 additions & 0 deletions client/src/lib/pro.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { LanguageClient } from 'vscode-languageclient/node';
import { phpstanProNotification } from './notificationChannels';
import type { Disposable } from 'vscode';
import { env, window } from 'vscode';
import { Uri } from 'vscode';

export class PHPStanProManager implements Disposable {
private _disposables: Disposable[] = [];
public port: number | null = null;

public constructor(client: LanguageClient) {
this._disposables.push(
// eslint-disable-next-line @typescript-eslint/no-misused-promises
client.onNotification(phpstanProNotification, async (message) => {
if (message.type === 'setPort') {
this.port = message.port;
} else if (message.type === 'requireLogin') {
const choice = await window.showInformationMessage(
'Please log in to PHPStan Pro',
'Log in'
);
if (choice === 'Log in') {
if (!this.port) {
void window.showErrorMessage(
'PHPStan Pro port is unknown'
);
} else {
void env.openExternal(
Uri.parse(`http://127.0.0.1:${this.port}`)
);
}
}
}
})
);
}

public dispose(): void {
for (const disposable of this._disposables) {
disposable.dispose();
}
}
}
Loading

0 comments on commit ce2756d

Please sign in to comment.