Skip to content

Commit 54b4101

Browse files
committed
Fixes microsoft#19624: Adopt "php.validate.executablePath" handling to new TS behavior
1 parent 72a0322 commit 54b4101

3 files changed

Lines changed: 71 additions & 207 deletions

File tree

extensions/php/package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@
5252
"php.validate.executablePath": {
5353
"type": ["string", "null"],
5454
"default": null,
55-
"description": "%configuration.validate.executablePath%",
56-
"isExecutable": true
55+
"description": "%configuration.validate.executablePath%"
5756
},
5857
"php.validate.run": {
5958
"type": "string",

extensions/php/src/features/validationProvider.ts

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -79,26 +79,29 @@ namespace RunTrigger {
7979
};
8080
}
8181

82+
const CheckedExecutablePath = 'php.validate.checkedExecutablePath';
83+
8284
export default class PHPValidationProvider {
8385

8486
private static MatchExpression: RegExp = /(?:(?:Parse|Fatal) error): (.*)(?: in )(.*?)(?: on line )(\d+)/;
8587
private static BufferArgs: string[] = ['-l', '-n', '-d', 'display_errors=On', '-d', 'log_errors=Off'];
8688
private static FileArgs: string[] = ['-l', '-n', '-d', 'display_errors=On', '-d', 'log_errors=Off', '-f'];
8789

8890
private validationEnabled: boolean;
91+
private executableIsUserDefined: boolean;
8992
private executable: string;
9093
private trigger: RunTrigger;
91-
private executableNotFound: boolean;
94+
private pauseValidation: boolean;
9295

9396
private documentListener: vscode.Disposable;
9497
private diagnosticCollection: vscode.DiagnosticCollection;
9598
private delayers: { [key: string]: ThrottledDelayer<void> };
9699

97-
constructor(private workspaceExecutablePath: string) {
100+
constructor(private workspaceStore: vscode.Memento) {
98101
this.executable = null;
99102
this.validationEnabled = true;
100103
this.trigger = RunTrigger.onSave;
101-
this.executableNotFound = false;
104+
this.pauseValidation = false;
102105
}
103106

104107
public activate(subscriptions: vscode.Disposable[]) {
@@ -114,17 +117,6 @@ export default class PHPValidationProvider {
114117
}, null, subscriptions);
115118
}
116119

117-
public updateWorkspaceExecutablePath(workspaceExecutablePath: string, loadConfig: boolean = false): void {
118-
if (workspaceExecutablePath && workspaceExecutablePath.length === 0) {
119-
this.workspaceExecutablePath = undefined;
120-
} else {
121-
this.workspaceExecutablePath = workspaceExecutablePath;
122-
}
123-
if (loadConfig) {
124-
this.loadConfiguration();
125-
}
126-
}
127-
128120
public dispose(): void {
129121
this.diagnosticCollection.clear();
130122
this.diagnosticCollection.dispose();
@@ -135,12 +127,22 @@ export default class PHPValidationProvider {
135127
let oldExecutable = this.executable;
136128
if (section) {
137129
this.validationEnabled = section.get<boolean>('validate.enable', true);
138-
this.executable = this.workspaceExecutablePath || section.get<string>('validate.executablePath', null);
130+
let inspect = section.inspect<string>('validate.executablePath');
131+
if (inspect.workspaceValue) {
132+
this.executable = inspect.workspaceValue;
133+
this.executableIsUserDefined = false;
134+
} else if (inspect.globalValue) {
135+
this.executable = inspect.globalValue;
136+
this.executableIsUserDefined = true;
137+
} else {
138+
this.executable = undefined;
139+
this.executableIsUserDefined = undefined;
140+
}
139141
this.trigger = RunTrigger.from(section.get<string>('validate.run', RunTrigger.strings.onSave));
140142
}
141143
this.delayers = Object.create(null);
142-
if (this.executableNotFound) {
143-
this.executableNotFound = oldExecutable === this.executable;
144+
if (this.pauseValidation) {
145+
this.pauseValidation = oldExecutable === this.executable;
144146
}
145147
if (this.documentListener) {
146148
this.documentListener.dispose();
@@ -160,16 +162,56 @@ export default class PHPValidationProvider {
160162
}
161163

162164
private triggerValidate(textDocument: vscode.TextDocument): void {
163-
if (textDocument.languageId !== 'php' || this.executableNotFound || !this.validationEnabled) {
165+
if (textDocument.languageId !== 'php' || this.pauseValidation || !this.validationEnabled) {
164166
return;
165167
}
166-
let key = textDocument.uri.toString();
167-
let delayer = this.delayers[key];
168-
if (!delayer) {
169-
delayer = new ThrottledDelayer<void>(this.trigger === RunTrigger.onType ? 250 : 0);
170-
this.delayers[key] = delayer;
168+
169+
interface MessageItem extends vscode.MessageItem {
170+
id: string;
171+
}
172+
173+
let trigger = () => {
174+
let key = textDocument.uri.toString();
175+
let delayer = this.delayers[key];
176+
if (!delayer) {
177+
delayer = new ThrottledDelayer<void>(this.trigger === RunTrigger.onType ? 250 : 0);
178+
this.delayers[key] = delayer;
179+
}
180+
delayer.trigger(() => this.doValidate(textDocument));
181+
};
182+
183+
if (this.executableIsUserDefined !== void 0 && !this.executableIsUserDefined) {
184+
let checkedExecutablePath = this.workspaceStore.get<string>(CheckedExecutablePath, undefined);
185+
if (!checkedExecutablePath || checkedExecutablePath !== this.executable) {
186+
vscode.window.showInformationMessage<MessageItem>(
187+
localize('php.useExecutablePath', 'Do you allow {0} to be executed to lint this file?', this.executable),
188+
{
189+
title: localize('php.yes', 'Yes'),
190+
id: 'yes'
191+
},
192+
{
193+
title: localize('php.no', 'No'),
194+
isCloseAffordance: true,
195+
id: 'no'
196+
},
197+
{
198+
title: localize('php.more', 'Learn More'),
199+
id: 'more'
200+
}
201+
).then(selected => {
202+
if (!selected || selected.id === 'no') {
203+
this.pauseValidation = true;
204+
} else if (selected.id === 'yes') {
205+
this.workspaceStore.update(CheckedExecutablePath, this.executable);
206+
trigger();
207+
} else if (selected.id === 'more') {
208+
vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=839878'));
209+
}
210+
});
211+
return;
212+
}
171213
}
172-
delayer.trigger(() => this.doValidate(textDocument));
214+
trigger();
173215
}
174216

175217
private doValidate(textDocument: vscode.TextDocument): Promise<void> {
@@ -201,12 +243,12 @@ export default class PHPValidationProvider {
201243
try {
202244
let childProcess = cp.spawn(executable, args, options);
203245
childProcess.on('error', (error: Error) => {
204-
if (this.executableNotFound) {
246+
if (this.pauseValidation) {
205247
resolve();
206248
return;
207249
}
208250
this.showError(error, executable);
209-
this.executableNotFound = true;
251+
this.pauseValidation = true;
210252
resolve();
211253
});
212254
if (childProcess.pid) {

extensions/php/src/phpMain.ts

Lines changed: 2 additions & 179 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@
44
*--------------------------------------------------------------------------------------------*/
55
'use strict';
66

7-
import * as fs from 'fs';
8-
import * as path from 'path';
9-
107
import PHPCompletionItemProvider from './features/completionItemProvider';
118
import PHPHoverProvider from './features/hoverProvider';
129
import PHPSignatureHelpProvider from './features/signatureHelpProvider';
@@ -15,63 +12,11 @@ import * as vscode from 'vscode';
1512

1613
import * as nls from 'vscode-nls';
1714
nls.config({ locale: vscode.env.language });
18-
let localize = nls.loadMessageBundle();
19-
20-
const MigratedKey = 'php.validate.executablePaht.migrated';
21-
const PathKey = 'php.validate.executablePath';
22-
23-
namespace is {
24-
const toString = Object.prototype.toString;
25-
26-
export function string(value: any): value is string {
27-
return toString.call(value) === '[object String]';
28-
}
29-
}
30-
31-
let statusBarItem: vscode.StatusBarItem;
3215

3316
export function activate(context: vscode.ExtensionContext): any {
3417

35-
let workspaceExecutablePath = context.workspaceState.get<string>(PathKey, undefined);
36-
let migrated = context.workspaceState.get<boolean>(MigratedKey, false);
37-
let validator = new PHPValidationProvider(workspaceExecutablePath);
38-
context.subscriptions.push(vscode.commands.registerCommand('_php.onPathClicked', () => {
39-
onPathClicked(context, validator);
40-
}));
41-
42-
statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, Number.MIN_VALUE);
43-
statusBarItem.text = localize('php.path', 'Path');
44-
statusBarItem.color = 'white';
45-
statusBarItem.command = '_php.onPathClicked';
46-
vscode.workspace.onDidChangeConfiguration(() => updateStatusBarItem(context));
47-
vscode.window.onDidChangeActiveTextEditor((editor) => {
48-
updateStatusBarItem(context, editor);
49-
});
50-
updateStatusBarItem(context, vscode.window.activeTextEditor);
51-
52-
if (workspaceExecutablePath === void 0 && !migrated) {
53-
let settingsExecutablePath = readLocalExecutableSetting();
54-
if (settingsExecutablePath) {
55-
migrateExecutablePath(settingsExecutablePath).then((value) => {
56-
context.workspaceState.update(MigratedKey, true);
57-
// User has pressed escape;
58-
if (!value) {
59-
// activate the validator with the current settings.
60-
validator.activate(context.subscriptions);
61-
return;
62-
}
63-
context.workspaceState.update(PathKey, value);
64-
validator.updateWorkspaceExecutablePath(value, false);
65-
validator.activate(context.subscriptions);
66-
updateStatusBarItem(context);
67-
});
68-
} else {
69-
context.workspaceState.update(MigratedKey, true);
70-
validator.activate(context.subscriptions);
71-
}
72-
} else {
73-
validator.activate(context.subscriptions);
74-
}
18+
let validator = new PHPValidationProvider(context.workspaceState);
19+
validator.activate(context.subscriptions);
7520

7621
// add providers
7722
context.subscriptions.push(vscode.languages.registerCompletionItemProvider('php', new PHPCompletionItemProvider(), '.', '$'));
@@ -83,126 +28,4 @@ export function activate(context: vscode.ExtensionContext): any {
8328
vscode.languages.setLanguageConfiguration('php', {
8429
wordPattern: /(-?\d*\.\d\w*)|([^\-\`\~\!\@\#\%\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g
8530
});
86-
}
87-
88-
function updateStatusBarItem(context: vscode.ExtensionContext, editor: vscode.TextEditor = vscode.window.activeTextEditor): void {
89-
statusBarItem.tooltip = getExecutablePath(context);
90-
if (editor && editor.document && editor.document.languageId === 'php') {
91-
statusBarItem.show();
92-
} else {
93-
statusBarItem.hide();
94-
}
95-
}
96-
97-
function onPathClicked(context: vscode.ExtensionContext, validator: PHPValidationProvider) {
98-
let value = getExecutablePath(context);
99-
vscode.window.showInputBox({ prompt: localize('php.enterPath', 'The path to the PHP executable'), value: value || '' }).then(value => {
100-
if (!value) {
101-
// User pressed Escape
102-
return;
103-
}
104-
context.workspaceState.update(PathKey, value);
105-
validator.updateWorkspaceExecutablePath(value, true);
106-
updateStatusBarItem(context);
107-
}, (error) => {
108-
});
109-
}
110-
111-
function getExecutablePath(context: vscode.ExtensionContext): string {
112-
let result = context.workspaceState.get<string>(PathKey, undefined);
113-
if (result) {
114-
return result;
115-
}
116-
let section = vscode.workspace.getConfiguration('php.validate');
117-
if (section) {
118-
return section.get('executablePath', undefined);
119-
}
120-
return undefined;
121-
}
122-
123-
function migrateExecutablePath(settingsExecutablePath: string): Thenable<string> {
124-
return vscode.window.showInformationMessage(
125-
localize('php.migrateWorkspaceSetting', 'Do you want to use {0} as your future PHP executable path?', settingsExecutablePath),
126-
{
127-
title: localize('php.yes', 'Yes'),
128-
id: 'yes'
129-
},
130-
{
131-
title: localize('php.edit', 'Edit'),
132-
id: 'edit'
133-
},
134-
{
135-
title: localize('php.more', 'Learn More'),
136-
id: 'more'
137-
}
138-
).then((selected) => {
139-
if (!selected) {
140-
return undefined;
141-
}
142-
if (selected.id === 'yes') {
143-
return settingsExecutablePath;
144-
} else if (selected.id === 'edit') {
145-
return vscode.window.showInputBox(
146-
{
147-
prompt: localize('php.migrateExecutablePath', 'Use the above path as the PHP executable path?'),
148-
value: settingsExecutablePath
149-
}
150-
);
151-
} else if (selected.id === 'more') {
152-
vscode.commands.executeCommand('vscode.open', vscode.Uri.parse('https://go.microsoft.com/fwlink/?linkid=839919'));
153-
return undefined;
154-
}
155-
});
156-
}
157-
158-
function readLocalExecutableSetting(): string {
159-
function stripComments(content: string): string {
160-
/**
161-
* First capturing group matches double quoted string
162-
* Second matches single quotes string
163-
* Third matches block comments
164-
* Fourth matches line comments
165-
*/
166-
var regexp: RegExp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g;
167-
let result = content.replace(regexp, (match, m1, m2, m3, m4) => {
168-
// Only one of m1, m2, m3, m4 matches
169-
if (m3) {
170-
// A block comment. Replace with nothing
171-
return '';
172-
} else if (m4) {
173-
// A line comment. If it ends in \r?\n then keep it.
174-
let length = m4.length;
175-
if (length > 2 && m4[length - 1] === '\n') {
176-
return m4[length - 2] === '\r' ? '\r\n' : '\n';
177-
} else {
178-
return '';
179-
}
180-
} else {
181-
// We match a string
182-
return match;
183-
}
184-
});
185-
return result;
186-
};
187-
188-
try {
189-
let rootPath = vscode.workspace.rootPath;
190-
if (!rootPath) {
191-
return undefined;
192-
}
193-
let settingsFile = path.join(rootPath, '.vscode', 'settings.json');
194-
if (!fs.existsSync(settingsFile)) {
195-
return undefined;
196-
}
197-
let content = fs.readFileSync(settingsFile, 'utf8');
198-
if (!content || content.length === 0) {
199-
return undefined;
200-
}
201-
content = stripComments(content);
202-
let json = JSON.parse(content);
203-
let value = json['php.validate.executablePath'];
204-
return is.string(value) ? value : undefined;
205-
} catch (error) {
206-
}
207-
return undefined;
20831
}

0 commit comments

Comments
 (0)