Skip to content

Commit

Permalink
Add multi-workspace support
Browse files Browse the repository at this point in the history
  • Loading branch information
Sander Ronde committed Oct 15, 2024
1 parent 2d1c174 commit 2220465
Show file tree
Hide file tree
Showing 23 changed files with 369 additions and 89 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ test/demo/reported.json
test/multi-config-demo/vendor
test/multi-config-demo/cache
test/multi-config-demo/reported.json
test/multi-workspace-demo/primary/vendor
test/multi-workspace-demo/primary/cache
test/multi-workspace-demo/secondary/vendor
test/multi-workspace-demo/secondary/cache
test/scratchpad
**/reported.json
user_config.json
Expand Down
27 changes: 1 addition & 26 deletions client/src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
import {
getReadonlyEditorConfiguration,
getWritableEditorConfiguration,
registerEditorConfigurationListener,
} from './lib/editorConfig';
import {
createOutputChannel,
SERVER_PREFIX,
Expand All @@ -19,6 +14,7 @@ import type {
ServerOptions,
} from 'vscode-languageclient/node';
import { LanguageClient, TransportKind } from 'vscode-languageclient/node';
import { registerEditorConfigurationListener } from './lib/editorConfig';
import { DocumentManager } from './notificationSenders/documentManager';
import { ErrorManager } from './notificationReceivers/errorManager';
import { ZombieKiller } from './notificationReceivers/zombieKiller';
Expand Down Expand Up @@ -128,27 +124,6 @@ export async function activate(context: ExtensionContext): Promise<void> {
})
);
log(CLIENT_PREFIX, 'Initializing done');

void (async () => {
if (
workspace.workspaceFolders &&
workspace.workspaceFolders?.length > 1 &&
!getReadonlyEditorConfiguration().suppressWorkspaceMessage
) {
const SUPPRESS_OPTION = "Don't show again";
const choice = await window.showWarningMessage(
`PHPStan extension only supports single-workspace projects, it'll only use the first workspace folder (${workspace.workspaceFolders[0].name}`,
SUPPRESS_OPTION
);
if (choice === SUPPRESS_OPTION) {
await getWritableEditorConfiguration().update(
'phpstan.suppressWorkspaceMessage',
true
);
}
}
})();

log(CLIENT_PREFIX, 'Showing one-time messages (if needed)');
const installationConfig = await getInstallationConfig(context);
const version = (context.extension.packageJSON as { version: string })
Expand Down
24 changes: 15 additions & 9 deletions client/src/lib/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,16 @@ function toKeyedWorkspaceFolders(
const uri = workspaceFolders?.[0].uri;
if (uri) {
const initializedFolders: WorkspaceFolders = {
default: uri,
byName: {},
getForPath: (filePath: string) => {
return workspace.getWorkspaceFolder(Uri.file(filePath))?.uri;
},
};
if (workspaceFolders?.length === 1) {
initializedFolders.default = uri;
}
for (const folder of workspaceFolders ?? []) {
initializedFolders[folder.name] = folder.uri;
initializedFolders.byName[folder.name] = folder.uri;
}
return initializedFolders;
}
Expand Down Expand Up @@ -202,7 +208,7 @@ abstract class SetupSteps {
) {}

protected _getCwd(): string | undefined {
return this._workspaceFolders?.default.fsPath;
return this._workspaceFolders?.default?.fsPath;
}

protected async _rootDirStep(
Expand Down Expand Up @@ -242,9 +248,9 @@ abstract class SetupSteps {
title: 'Select root directory',
});
if (folder) {
if (this._workspaceFolders?.default.fsPath === folder[0].fsPath) {
if (this._workspaceFolders?.default?.fsPath === folder[0].fsPath) {
this._state.rootDir = './';
} else if (this._workspaceFolders) {
} else if (this._workspaceFolders?.default) {
this._state.rootDir = path.relative(
this._workspaceFolders.default.fsPath,
folder[0].fsPath
Expand Down Expand Up @@ -300,7 +306,7 @@ abstract class SetupSteps {
});
if (file) {
this._state.configFiles = [
this._workspaceFolders
this._workspaceFolders?.default
? path.relative(
this._workspaceFolders.default.fsPath,
file[0].fsPath
Expand Down Expand Up @@ -358,7 +364,7 @@ abstract class SetupSteps {
title: 'Select PHPStan binary',
});
if (file) {
this._state.binPath = this._workspaceFolders
this._state.binPath = this._workspaceFolders?.default
? path.relative(
this._workspaceFolders.default.fsPath,
file[0].fsPath
Expand Down Expand Up @@ -455,7 +461,7 @@ abstract class SetupSteps {
label: path.relative(
makeAbsolute(
this._state.rootDir,
this._workspaceFolders?.default.fsPath
this._workspaceFolders?.default?.fsPath
),
uri.fsPath
),
Expand Down Expand Up @@ -682,7 +688,7 @@ class DockerSetupSteps extends SetupSteps {
title: 'Select PHPStan binary',
});
if (file) {
this._state.binPath = this._workspaceFolders
this._state.binPath = this._workspaceFolders?.default
? path.relative(
this._workspaceFolders.default.fsPath,
file[0].fsPath
Expand Down
74 changes: 54 additions & 20 deletions server/src/lib/checkConfigManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as os from 'os';
export interface CheckConfig {
cwd: string;
configFile: string | null;
workspaceRoot: string | undefined;
remoteConfigFile: string | null;
getBinCommand: (args: string[]) => string[];
args: string[];
Expand All @@ -33,11 +34,11 @@ export class ConfigurationManager {

public static async applyPathMapping(
classConfig: ClassConfig,
filePath: string
filePath: string,
workspaceRoot: string | undefined
): Promise<string> {
const paths = (await getEditorConfiguration(classConfig)).paths;
const cwd = (await classConfig.workspaceFolders.get())?.default.fsPath;
return getPathMapper(paths, cwd)(filePath);
return getPathMapper(paths, workspaceRoot)(filePath);
}

private static async _fileIfExists(
Expand Down Expand Up @@ -93,7 +94,7 @@ export class ConfigurationManager {

private static async _getConfigFile(
classConfig: ClassConfig,
cwd: string,
cwd: string | undefined,
currentFile: URI | null
): Promise<string | null> {
const extensionConfig = await getEditorConfiguration(classConfig);
Expand Down Expand Up @@ -167,7 +168,8 @@ export class ConfigurationManager {

public static async getBinComand(
classConfig: ClassConfig,
cwd: string
cwd: string,
workspaceRoot: string | undefined
): Promise<
| {
success: true;
Expand All @@ -187,7 +189,11 @@ export class ConfigurationManager {
if (binPath.startsWith('~')) {
binPath = `${process.env.HOME ?? '~'}${binPath.slice(1)}`;
}
binPath = await this.applyPathMapping(classConfig, binPath);
binPath = await this.applyPathMapping(
classConfig,
binPath,
workspaceRoot
);

const binCommand = extensionConfig.binCommand;
if (
Expand Down Expand Up @@ -239,11 +245,48 @@ export class ConfigurationManager {
// Settings
const extensionConfig = await getEditorConfiguration(classConfig);

const cwd = await this.getCwd(classConfig);
const workspaceFolders = await classConfig.workspaceFolders.get();
let cwd: string | undefined;
if (workspaceFolders?.default) {
cwd = (await this.getCwd(classConfig)) ?? undefined;
if (!cwd) {
return null;
}
} else {
// Multiple workspaces. This means config files need to be absolute and we can
// use that to infer cwd.
}

const configFile = await this._getConfigFile(
classConfig,
cwd,
currentFile
);
if (!configFile) {
if (onError) {
onError('Failed to find config file');
}
return null;
}
const workspaceRoot =
workspaceFolders?.getForPath(configFile)?.fsPath ??
workspaceFolders?.default?.fsPath;

if (!cwd) {
cwd =
this._getAbsolutePath(extensionConfig.rootDir, workspaceRoot) ||
workspaceRoot;
}

if (!cwd) {
await showErrorOnce(
classConfig.connection,
'PHPStan: failed to get CWD'
);
return null;
}
const result = await this.getBinComand(classConfig, cwd);

const result = await this.getBinComand(classConfig, cwd, workspaceRoot);
if (!result.success) {
if (onError) {
onError(result.error);
Expand All @@ -255,27 +298,18 @@ export class ConfigurationManager {
}
return null;
}
const configFile = await this._getConfigFile(
classConfig,
cwd,
currentFile
);
if (!configFile) {
if (onError) {
onError('Failed to find config file');
}
return null;
}

const tmpDir: string | undefined = extensionConfig.tmpDir;

return {
cwd,
configFile,
workspaceRoot,
remoteConfigFile: configFile
? await ConfigurationManager.applyPathMapping(
classConfig,
configFile
configFile,
cwd
)
: null,
args: extensionConfig.options ?? [],
Expand Down
11 changes: 5 additions & 6 deletions server/src/lib/documentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,12 +280,11 @@ export class DocumentManager implements AsyncDisposable {

const editorConfig = await getEditorConfiguration(this._classConfig);
if (!editorConfig.singleFileMode) {
await checkManager.checkWithDebounce(
undefined,
e ? URI.parse(e.uri) : null,
'Config change',
null
);
if ((await this._classConfig.workspaceFolders.get())?.default) {
await this._onScanCurrentProject(checkManager, e);
} else {
await this._onScanAllProjects(checkManager);
}
}
void this.watcher?.onConfigChange();
}
Expand Down
2 changes: 0 additions & 2 deletions server/src/lib/editorConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,9 @@ export async function getEditorConfiguration(
>
): Promise<Omit<ConfigWithoutPrefix<ConfigSettings>, 'enableLanguageServer'>> {
const workspaceFolders = await classConfig.workspaceFolders.get();
const scope = workspaceFolders?.default.toString();

const editorConfig = {
...((await classConfig.connection.workspace.getConfiguration({
scopeUri: scope,
section: 'phpstan',
})) as ConfigWithoutPrefix<ConfigSettings> &
ConfigWithoutPrefix<DeprecatedConfigSettings>),
Expand Down
5 changes: 3 additions & 2 deletions server/src/lib/phpstan/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ export class PHPStanCheck implements AsyncDisposable {
// Get file
const filePath = await ConfigurationManager.applyPathMapping(
this._classConfig,
URI.parse(file.uri).fsPath
URI.parse(file.uri).fsPath,
checkConfig.cwd
);

const result = await runner.runProcess<PHPStanCheckResult>(
Expand Down Expand Up @@ -185,7 +186,7 @@ export class PHPStanCheck implements AsyncDisposable {
const errorManager = new PHPStanCheckErrorManager(this._classConfig);
const pathMapper = getPathMapper(
(await getEditorConfiguration(this._classConfig)).paths,
(await this._classConfig.workspaceFolders.get())?.default.fsPath
checkConfig.workspaceRoot
);
const runner = new PHPStanRunner(this._classConfig);
this.disposables.push(runner);
Expand Down
2 changes: 1 addition & 1 deletion server/src/lib/phpstan/pro/proErrorManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ export class PHPStanProErrorManager implements Disposable {

this._pathMapper ??= getPathMapper(
(await getEditorConfiguration(this._classConfig)).paths,
(await this._classConfig.workspaceFolders.get())?.default.fsPath
(await this._classConfig.workspaceFolders.get())?.default?.fsPath
);
const fileSpecificErrors: ReportedErrors['fileSpecificErrors'] = {};
for (const fileError of errors.fileSpecificErrors) {
Expand Down
7 changes: 5 additions & 2 deletions server/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ export interface ClassConfig {
}

export type WorkspaceFolders = {
[name: string]: URI | undefined;
default: URI;
byName: {
[name: string]: URI | undefined;
};
getForPath: (path: string) => URI | undefined;
default?: URI;
};

export class PromisedValue<V> {
Expand Down
14 changes: 4 additions & 10 deletions server/src/providers/providerUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,6 @@ export async function getFileReport(
return null;
}

const workspaceFolder = (await providerArgs.workspaceFolders.get())
?.default;
if (
!workspaceFolder ||
(!NO_CANCEL_OPERATIONS && cancelToken.isCancellationRequested)
) {
return null;
}

// Ensure the file has been checked
if (!providerArgs.phpstan) {
return (
Expand Down Expand Up @@ -145,10 +136,13 @@ export class ProviderCheckHooks {
return null;
}

const roots = Object.values(workspaceFolder.byName).map((root) =>
root?.toString()
);
return path.join(
(await this._extensionPath.get()).fsPath,
'_config',
basicHash(workspaceFolder.default.fsPath)
basicHash(JSON.stringify(roots))
);
}

Expand Down
Loading

0 comments on commit 2220465

Please sign in to comment.