Skip to content

Commit

Permalink
Merge pull request #58 from SanderRonde/pro
Browse files Browse the repository at this point in the history
  • Loading branch information
SanderRonde authored Feb 24, 2024
2 parents f216e06 + 2b4ccbc commit 3374dbc
Show file tree
Hide file tree
Showing 49 changed files with 2,166 additions and 795 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ php/vendor
test/demo/vendor
test/demo/reported.json
test/scratchpad
**/reported.json
**/reported.json
user_config.json

_config
4 changes: 3 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
"request": "launch",
"args": [
"--disable-extensions",
"--extensionDevelopmentPath=${workspaceFolder}"
"--extensionDevelopmentPath=${workspaceFolder}",
"--enable-proposed-api",
"SanderRonde.phpstan-vscode"
],
"outFiles": ["${workspaceFolder}/out/**/*.js"],
"preLaunchTask": "build-debug"
Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

All notable changes to the "phpstan-vscode" extension will be documented in this file.

### 3.0.1

- Only check on initial startup if the extension is enabled

### 3.0.0

- Always perform whole-repository checks instead of single-file checks
- Ensures cache is always hit
- Ensures relevant file changes in other files are always picked up
- Add support for PHPStan Pro
- Uses PHPStan Pro to watch files while displaying its errors in the editor
- (Requires a valid PHPStan Pro license)
- Language server improvements
- Now indexes the entire project at once (no more waiting for individual file checks)
- Now uses the exact location of variables (this was previously guessed because PHPStan didn't provide information regarding the index of a value on a line)
- Includes function and closure arguments now too

### 2.2.26

- Add warning for multi-workspace projects
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ https://user-images.githubusercontent.com/5385012/188924277-c9392477-9bd6-40b1-9
- `phpstan.rootDir` - path to the root directory of your PHP project (defaults to `workspaceFolder`)
- `phpstan.binPath` - path to the PHPStan binary (defaults to `${workspaceFolder}/vendor/bin/phpstan`)
- `phpstan.binCommand` - command that runs the PHPStan binary. Use this if, for example, PHPStan is already in your global path. If this is specified, it is used instead of `phpstan.binPath`. Unset by default.
- `phpstan.pro` - Enable PHPStan Pro support. Runs PHPStan Pro in the background and leaves watching to PHPStan while displaying any errors it catches in the editor. This requires a valid license. False by default.

### Tuning

Expand All @@ -26,6 +27,7 @@ https://user-images.githubusercontent.com/5385012/188924277-c9392477-9bd6-40b1-9
- `phpstan.suppressTimeoutMessage` - whether to disable the error message when the check times out (defaults to `false`)
- `phpstan.paths` - path mapping that allows for rewriting paths. Can be useful when developing inside a docker container or over SSH. Unset by default. Example for making the extension work in a docker container: `{ "/path/to/hostFolder": "/path/in/dockerContainer" }`
- `phpstan.ignoreErrors` - An array of regular expressions to ignore in error messages. If you find the PHPStan process erroring often because of a warning that can be ignored, put the warning in here and it'll be ignored in the future.
- `phpstan.proTmpDir` - Path to the PHPStan Pro TMP directory. Defaults to PHPStan's default (which is `$TMPDIR/phpstan-fixer`)

### Customization

Expand Down
22 changes: 18 additions & 4 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@ import type {
} from 'vscode-languageclient/node';
import { LanguageClient, TransportKind } from 'vscode-languageclient/node';
import { getConfiguration, registerConfigListeners } from './lib/config';
import { readyNotification } from './lib/notificationChannels';
import { DocumentManager } from './lib/documentManager';
import { initRequest } from './lib/requestChannels';
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 Down Expand Up @@ -84,15 +85,23 @@ 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;
const startedAt = Date.now();
context.subscriptions.push(
client.onNotification(readyNotification, ({ ready }) => {
client.onRequest(initRequest, ({ ready }) => {
if (ready) {
if (!wasReady) {
// First time it's ready, start watching
Expand All @@ -107,6 +116,11 @@ export async function activate(context: ExtensionContext): Promise<void> {
}
wasReady = true;
}

return Promise.resolve({
extensionPath: context.extensionUri.toString(),
startedAt: startedAt,
});
})
);
log(CLIENT_PREFIX, 'Initializing done');
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
10 changes: 10 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,15 @@ 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') ||
(e.affectsConfiguration('phpstan.enabled') &&
getConfiguration().get('phpstan.pro'))
) {
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.uri.toString(),
this._errors.fileSpecificErrors.get(
lastEditor.document.uri.toString()
) ?? []
);
}

if (editor) {
this._showErrors(editor.document.uri.toString(), [
...(this._errors.fileSpecificErrors.get(
editor.document.uri.toString()
) ?? []),
...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
1 change: 1 addition & 0 deletions client/src/lib/log.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ export const STATUS_BAR_PREFIX = '[status-bar]' as Prefix;
export const CLIENT_PREFIX = '[client]' as Prefix;
export const SERVER_PREFIX = '[server]' as Prefix;
export const ERROR_PREFIX = '[error]' as Prefix;
export const PROCESS_SPAWNER_PREFIX = '[process-spawner]' as Prefix;
11 changes: 6 additions & 5 deletions client/src/lib/notificationChannels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import type {
CommandNotificationType,
ErrorNotificationType,
LogNotificationType,
PHPStanProNotificationType,
ProcessNotificationType,
ReadyNotificationType,
StatusBarNotificationType,
WatcherNotificationType,
} from '../../../shared/notificationChannels';
Expand All @@ -25,13 +25,14 @@ export const statusBarNotification =
NotificationChannel.STATUS_BAR
);

export const readyNotification = new NotificationType<ReadyNotificationType>(
NotificationChannel.READY
);

export const errorNotification = new NotificationType<ErrorNotificationType>(
NotificationChannel.ERROR
);

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

export const phpstanProNotification =
new NotificationType<PHPStanProNotificationType>(
NotificationChannel.PHPSTAN_PRO
);
66 changes: 66 additions & 0 deletions client/src/lib/pro.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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}`)
);
}
}
}
})
);

// // eslint-disable-next-line @typescript-eslint/no-this-alias
// const self = this;
// if (typeof workspace.registerPortAttributesProvider === 'function') {
// workspace.registerPortAttributesProvider(
// {},
// new (class implements PortAttributesProvider {
// public providePortAttributes(attributes: {
// port: number;
// pid?: number | undefined;
// commandLine?: string | undefined;
// }): ProviderResult<PortAttributes> {
// if (attributes.port !== self.port) {
// return undefined;
// }

// return {
// autoForwardAction: PortAutoForwardAction.Silent,
// };
// }
// })()
// );
// }
}

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

0 comments on commit 3374dbc

Please sign in to comment.