From 9516c8de26b44dbf5a0f3bc9a6ccf22614bb9186 Mon Sep 17 00:00:00 2001 From: Nitesh Kumar <38075523+niteskum@users.noreply.github.com> Date: Wed, 3 Apr 2019 14:12:03 +0530 Subject: [PATCH] Php Tooling Extensions Using LSP Framework (#14671) * Php Tooling Extensions Using LSP Framework * Corrected Indentation space * Corrected ESLint Error * addressed review comments * Addressed review comments * addressed review comments * Added Preferences description string * Addressed review comments * Addressed review comments * addressed review comments * Addresed review comments * Addresed review comments * Addressed some bug * Adding Unit test Files * Corrected the strings * Fixed Eslint Errors * Added some unit test Cases * using restart function * Switch to php7 in travis before dependency installation php-Tooling npm package requires felixfbecker/language-server php package, which can only be installed on php7. this should fix the break in brackets travis build --- .travis.yml | 2 + .../default/PhpTooling/CodeHintsProvider.js | 160 ++++++ src/extensions/default/PhpTooling/client.js | 120 +++++ .../default/PhpTooling/composer.json | 7 + src/extensions/default/PhpTooling/main.js | 231 ++++++++ .../default/PhpTooling/package.json | 14 + .../default/PhpTooling/phpGlobals.json | 75 +++ .../PhpTooling/unittest-files/mac/invalidphp | Bin 0 -> 10292 bytes .../PhpTooling/unittest-files/test/test1.php | 2 + .../PhpTooling/unittest-files/test/test2.php | 30 ++ .../PhpTooling/unittest-files/test/test3.php | 10 + .../unittest-files/win/invalidphp.exe | Bin 0 -> 97280 bytes .../default/PhpTooling/unittests.js | 499 ++++++++++++++++++ src/nls/root/strings.js | 15 +- 14 files changed, 1163 insertions(+), 2 deletions(-) create mode 100644 src/extensions/default/PhpTooling/CodeHintsProvider.js create mode 100644 src/extensions/default/PhpTooling/client.js create mode 100644 src/extensions/default/PhpTooling/composer.json create mode 100755 src/extensions/default/PhpTooling/main.js create mode 100644 src/extensions/default/PhpTooling/package.json create mode 100644 src/extensions/default/PhpTooling/phpGlobals.json create mode 100755 src/extensions/default/PhpTooling/unittest-files/mac/invalidphp create mode 100644 src/extensions/default/PhpTooling/unittest-files/test/test1.php create mode 100644 src/extensions/default/PhpTooling/unittest-files/test/test2.php create mode 100644 src/extensions/default/PhpTooling/unittest-files/test/test3.php create mode 100755 src/extensions/default/PhpTooling/unittest-files/win/invalidphp.exe create mode 100644 src/extensions/default/PhpTooling/unittests.js diff --git a/.travis.yml b/.travis.yml index 464408502..b2803e1d9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: node_js sudo: false # use container-based Travis infrastructure node_js: - "6" +before_install: + - phpenv global 7.0 #switch to php7, since that's what php-Tooling extension requires before_script: - npm install -g grunt-cli - npm install -g jasmine-node diff --git a/src/extensions/default/PhpTooling/CodeHintsProvider.js b/src/extensions/default/PhpTooling/CodeHintsProvider.js new file mode 100644 index 000000000..937f26476 --- /dev/null +++ b/src/extensions/default/PhpTooling/CodeHintsProvider.js @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2019 - present Adobe. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/* eslint-disable indent */ +/* eslint max-len: ["error", { "code": 200 }]*/ +define(function (require, exports, module) { + "use strict"; + + var _ = brackets.getModule("thirdparty/lodash"); + + var DefaultProviders = brackets.getModule("languageTools/DefaultProviders"), + EditorManager = brackets.getModule('editor/EditorManager'), + TokenUtils = brackets.getModule("utils/TokenUtils"), + StringMatch = brackets.getModule("utils/StringMatch"), + matcher = new StringMatch.StringMatcher({ + preferPrefixMatches: true + }); + + var phpSuperGlobalVariables = JSON.parse(require("text!phpGlobals.json")); + + function CodeHintsProvider(client) { + this.defaultCodeHintProviders = new DefaultProviders.CodeHintsProvider(client); + } + + function formatTypeDataForToken($hintObj, token) { + $hintObj.addClass('brackets-hints-with-type-details'); + if (token.detail) { + if (token.detail.trim() !== '?') { + if (token.detail.length < 30) { + $('' + token.detail.split('->').join(':').toString().trim() + '').appendTo($hintObj).addClass("brackets-hints-type-details"); + } + $('' + token.detail.split('->').join(':').toString().trim() + '').appendTo($hintObj).addClass("hint-description"); + } + } else { + if (token.keyword) { + $('keyword').appendTo($hintObj).addClass("brackets-hints-keyword"); + } + } + if (token.documentation) { + $hintObj.attr('title', token.documentation); + $('').text(token.documentation.trim()).appendTo($hintObj).addClass("hint-doc"); + } + } + + function filterWithQueryAndMatcher(hints, query) { + var matchResults = $.map(hints, function (hint) { + var searchResult = matcher.match(hint.label, query); + if (searchResult) { + for (var key in hint) { + searchResult[key] = hint[key]; + } + } + + return searchResult; + }); + + return matchResults; + } + + CodeHintsProvider.prototype.hasHints = function (editor, implicitChar) { + return this.defaultCodeHintProviders.hasHints(editor, implicitChar); + }; + + CodeHintsProvider.prototype.getHints = function (implicitChar) { + if (!this.defaultCodeHintProviders.client) { + return null; + } + + var editor = EditorManager.getActiveEditor(), + pos = editor.getCursorPos(), + docPath = editor.document.file._path, + $deferredHints = $.Deferred(), + self = this.defaultCodeHintProviders; + + this.defaultCodeHintProviders.client.requestHints({ + filePath: docPath, + cursorPos: pos + }).done(function (msgObj) { + var context = TokenUtils.getInitialContext(editor._codeMirror, pos), + hints = []; + + self.query = context.token.string.slice(0, context.pos.ch - context.token.start); + if (msgObj) { + var res = msgObj.items || []; + // There is a bug in Php Language Server, Php Language Server does not provide superGlobals + // Variables as completion. so these variables are being explicity put in response objects + // below code should be removed if php server fix this bug. + if(self.query) { + for(var key in phpSuperGlobalVariables) { + res.push({ + label: key, + documentation: phpSuperGlobalVariables[key].description, + detail: phpSuperGlobalVariables[key].type + }); + } + } + + var filteredHints = filterWithQueryAndMatcher(res, self.query); + + StringMatch.basicMatchSort(filteredHints); + filteredHints.forEach(function (element) { + var $fHint = $("") + .addClass("brackets-hints"); + + if (element.stringRanges) { + element.stringRanges.forEach(function (item) { + if (item.matched) { + $fHint.append($("") + .append(_.escape(item.text)) + .addClass("matched-hint")); + } else { + $fHint.append(_.escape(item.text)); + } + }); + } else { + $fHint.text(element.label); + } + + $fHint.data("token", element); + formatTypeDataForToken($fHint, element); + hints.push($fHint); + }); + } + + $deferredHints.resolve({ + "hints": hints + }); + }).fail(function () { + $deferredHints.reject(); + }); + + return $deferredHints; + }; + + CodeHintsProvider.prototype.insertHint = function ($hint) { + return this.defaultCodeHintProviders.insertHint($hint); + }; + + exports.CodeHintsProvider = CodeHintsProvider; +}); diff --git a/src/extensions/default/PhpTooling/client.js b/src/extensions/default/PhpTooling/client.js new file mode 100644 index 000000000..74f67d8b7 --- /dev/null +++ b/src/extensions/default/PhpTooling/client.js @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2019 - present Adobe. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ +/*global exports */ +/*global process */ +/*eslint-env es6, node*/ +/*eslint max-len: ["error", { "code": 200 }]*/ +"use strict"; + +var LanguageClient = require(global.LanguageClientInfo.languageClientPath).LanguageClient, + net = require("net"), + cp = require("child_process"), + execa = require("execa"), + semver = require('semver'), + clientName = "PhpClient", + executablePath = "", + memoryLimit = ""; + +function validatePhpExecutable(confParams) { + executablePath = confParams["executablePath"] || + (process.platform === 'win32' ? 'php.exe' : 'php'); + + memoryLimit = confParams["memoryLimit"] || '4095M'; + + return new Promise(function (resolve, reject) { + if (memoryLimit !== '-1' && !/^\d+[KMG]?$/.exec(memoryLimit)) { + reject("PHP_SERVER_MEMORY_LIMIT_INVALID"); + return; + } + + execa.stdout(executablePath, ['--version']).then(function (output) { + var matchStr = output.match(/^PHP ([^\s]+)/m); + if (!matchStr) { + reject("PHP_VERSION_INVALID"); + return; + } + var version = matchStr[1].split('-')[0]; + if (!/^\d+.\d+.\d+$/.test(version)) { + version = version.replace(/(\d+.\d+.\d+)/, '$1-'); + } + if (semver.lt(version, '7.0.0')) { + reject(["PHP_UNSUPPORTED_VERSION", version]); + return; + } + resolve(); + }).catch(function (err) { + if (err.code === 'ENOENT') { + reject("PHP_EXECUTABLE_NOT_FOUND"); + } else { + reject(["PHP_PROCESS_SPAWN_ERROR", err.code]); + console.error(err); + } + return; + }); + }); +} + +var serverOptions = function () { + return new Promise(function (resolve, reject) { + var server = net.createServer(function (socket) { + console.log('PHP process connected'); + socket.on('end', function () { + console.log('PHP process disconnected'); + }); + server.close(); + resolve({ + reader: socket, + writer: socket + }); + }); + server.listen(0, '127.0.0.1', function () { + var pathToPHP = __dirname + "/vendor/felixfbecker/language-server/bin/php-language-server.php"; + var childProcess = cp.spawn(executablePath, [ + pathToPHP, + '--tcp=127.0.0.1:' + server.address().port, + '--memory-limit=' + memoryLimit + ]); + childProcess.stderr.on('data', function (chunk) { + var str = chunk.toString(); + console.log('PHP Language Server:', str); + }); + childProcess.on('exit', function (code, signal) { + console.log( + "Language server exited " + (signal ? "from signal " + signal : "with exit code " + code) + ); + }); + return childProcess; + }); + }); + }, + options = { + serverOptions: serverOptions + }; + + +function init(domainManager) { + var client = new LanguageClient(clientName, domainManager, options); + client.addOnRequestHandler('validatePhpExecutable', validatePhpExecutable); +} + +exports.init = init; diff --git a/src/extensions/default/PhpTooling/composer.json b/src/extensions/default/PhpTooling/composer.json new file mode 100644 index 000000000..ce3968078 --- /dev/null +++ b/src/extensions/default/PhpTooling/composer.json @@ -0,0 +1,7 @@ +{ + "minimum-stability": "dev", + "prefer-stable": true, + "require": { + "felixfbecker/language-server": "^5.4" + } +} diff --git a/src/extensions/default/PhpTooling/main.js b/src/extensions/default/PhpTooling/main.js new file mode 100755 index 000000000..4d9221182 --- /dev/null +++ b/src/extensions/default/PhpTooling/main.js @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2019 - present Adobe. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ +define(function (require, exports, module) { + "use strict"; + + var LanguageTools = brackets.getModule("languageTools/LanguageTools"), + AppInit = brackets.getModule("utils/AppInit"), + ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), + ProjectManager = brackets.getModule("project/ProjectManager"), + EditorManager = brackets.getModule("editor/EditorManager"), + LanguageManager = brackets.getModule("language/LanguageManager"), + CodeHintManager = brackets.getModule("editor/CodeHintManager"), + ParameterHintManager = brackets.getModule("features/ParameterHintsManager"), + JumpToDefManager = brackets.getModule("features/JumpToDefManager"), + CodeInspection = brackets.getModule("language/CodeInspection"), + DefaultProviders = brackets.getModule("languageTools/DefaultProviders"), + CodeHintsProvider = require("CodeHintsProvider").CodeHintsProvider, + DefaultEventHandlers = brackets.getModule("languageTools/DefaultEventHandlers"), + PreferencesManager = brackets.getModule("preferences/PreferencesManager"), + Strings = brackets.getModule("strings"), + Dialogs = brackets.getModule("widgets/Dialogs"), + DefaultDialogs = brackets.getModule("widgets/DefaultDialogs"), + Commands = brackets.getModule("command/Commands"), + CommandManager = brackets.getModule("command/CommandManager"), + StringUtils = brackets.getModule("utils/StringUtils"); + + var clientFilePath = ExtensionUtils.getModulePath(module, "client.js"), + clientName = "PhpClient", + _client = null, + evtHandler, + phpConfig = { + enablePhpTooling: true, + executablePath: "php", + memoryLimit: "4095M", + validateOnType: "false" + }, + DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW = "debug.openPrefsInSplitView", + phpServerRunning = false, + serverCapabilities, + currentRootPath; + + PreferencesManager.definePreference("php", "object", phpConfig, { + description: Strings.DESCRIPTION_PHP_TOOLING_CONFIGURATION + }); + + PreferencesManager.on("change", "php", function () { + var newPhpConfig = PreferencesManager.get("php"); + + if ((newPhpConfig["executablePath"] !== phpConfig["executablePath"]) + || (newPhpConfig["enablePhpTooling"] !== phpConfig["enablePhpTooling"])) { + phpConfig = newPhpConfig; + runPhpServer(); + return; + } + phpConfig = newPhpConfig; + }); + + var handleProjectOpen = function (event, directory) { + if(serverCapabilities["workspace"] && serverCapabilities["workspace"]["workspaceFolders"]) { + _client.notifyProjectRootsChanged({ + foldersAdded: [directory.fullPath], + foldersRemoved: [currentRootPath] + }); + currentRootPath = directory.fullPath; + } else { + _client.restart({ + rootPath: directory.fullPath + }).done(handlePostPhpServerStart); + } + }; + + function registerToolingProviders() { + var chProvider = new CodeHintsProvider(_client), + phProvider = new DefaultProviders.ParameterHintsProvider(_client), + lProvider = new DefaultProviders.LintingProvider(_client), + jdProvider = new DefaultProviders.JumpToDefProvider(_client); + + JumpToDefManager.registerJumpToDefProvider(jdProvider, ["php"], 0); + CodeHintManager.registerHintProvider(chProvider, ["php"], 0); + ParameterHintManager.registerHintProvider(phProvider, ["php"], 0); + CodeInspection.register(["php"], { + name: Strings.PHP_DIAGNOSTICS, + scanFile: lProvider.getInspectionResults.bind(lProvider) + }); + + _client.addOnCodeInspection(lProvider.setInspectionResults.bind(lProvider)); + } + + function addEventHandlers() { + _client.addOnLogMessage(function () {}); + _client.addOnShowMessage(function () {}); + evtHandler = new DefaultEventHandlers.EventPropagationProvider(_client); + evtHandler.registerClientForEditorEvent(); + + + if (phpConfig["validateOnType"] !== "false") { + _client.addOnDocumentChangeHandler(function () { + CodeInspection.requestRun(Strings.PHP_DIAGNOSTICS); + }); + } + + _client.addOnProjectOpenHandler(handleProjectOpen); + } + + function validatePhpExecutable() { + var result = $.Deferred(); + + _client.sendCustomRequest({ + messageType: "brackets", + type: "validatePhpExecutable", + params: phpConfig + }).done(result.resolve).fail(result.reject); + + return result; + } + + function showErrorPopUp(err) { + if (typeof (err) === "string") { + err = Strings[err]; + } else { + err = StringUtils.format(Strings[err[0]], err[1]); + } + var Buttons = [ + { className: Dialogs.DIALOG_BTN_CLASS_NORMAL, id: Dialogs.DIALOG_BTN_CANCEL, + text: Strings.CANCEL }, + { className: Dialogs.DIALOG_BTN_CLASS_PRIMARY, id: Dialogs.DIALOG_BTN_DOWNLOAD, + text: Strings.OPEN_PREFERENNCES} + ]; + Dialogs.showModalDialog( + DefaultDialogs.DIALOG_ID_ERROR, + Strings.PHP_SERVER_ERROR_TITLE, + err, + Buttons + ).done(function (id) { + if (id === Dialogs.DIALOG_BTN_DOWNLOAD) { + if (CommandManager.get(DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW)) { + CommandManager.execute(DEBUG_OPEN_PREFERENCES_IN_SPLIT_VIEW); + } else { + CommandManager.execute(Commands.CMD_OPEN_PREFERENCES); + } + } + }); + } + + function handlePostPhpServerStart() { + if (!phpServerRunning) { + phpServerRunning = true; + registerToolingProviders(); + addEventHandlers(); + EditorManager.off("activeEditorChange.php"); + LanguageManager.off("languageModified.php"); + } + + evtHandler.handleActiveEditorChange(null, EditorManager.getActiveEditor()); + currentRootPath = ProjectManager.getProjectRoot()._path; + setTimeout(function () { + CodeInspection.requestRun(Strings.PHP_DIAGNOSTICS); + }, 1500); + } + + function runPhpServer() { + if (_client && phpConfig["enablePhpTooling"]) { + validatePhpExecutable() + .done(function () { + var startFunc = _client.start.bind(_client); + if (phpServerRunning) { + startFunc = _client.restart.bind(_client); + } + currentRootPath = ProjectManager.getProjectRoot()._path; + startFunc({ + rootPath: currentRootPath + }).done(function (result) { + console.log("php Language Server started"); + serverCapabilities = result.capabilities; + handlePostPhpServerStart(); + }); + }).fail(showErrorPopUp); + } + } + + function activeEditorChangeHandler(event, current) { + if (current) { + var language = current.document.getLanguage(); + if (language.getId() === "php") { + runPhpServer(); + EditorManager.off("activeEditorChange.php"); + LanguageManager.off("languageModified.php"); + } + } + } + + function languageModifiedHandler(event, language) { + if (language && language.getId() === "php") { + runPhpServer(); + EditorManager.off("activeEditorChange.php"); + LanguageManager.off("languageModified.php"); + } + } + + AppInit.appReady(function () { + LanguageTools.initiateToolingService(clientName, clientFilePath, ['php']).done(function (client) { + _client = client; + EditorManager.on("activeEditorChange.php", activeEditorChangeHandler); + LanguageManager.on("languageModified.php", languageModifiedHandler); + activeEditorChangeHandler(null, EditorManager.getActiveEditor()); + }); + }); + + //Only for Unit testing + exports.getClient = function() { return _client; }; +}); diff --git a/src/extensions/default/PhpTooling/package.json b/src/extensions/default/PhpTooling/package.json new file mode 100644 index 000000000..e9ef5096e --- /dev/null +++ b/src/extensions/default/PhpTooling/package.json @@ -0,0 +1,14 @@ +{ + "name": "php-Tooling", + "version": "1.0.0", + "description": "Advanced Tooling support for PHP", + "author": "niteskum", + "main": "main.js", + "scripts": { + "postinstall": "composer require felixfbecker/language-server && composer run-script --working-dir=vendor/felixfbecker/language-server parse-stubs" + }, + "dependencies": { + "execa": "1.0.0", + "semver": "5.6.0" + } +} diff --git a/src/extensions/default/PhpTooling/phpGlobals.json b/src/extensions/default/PhpTooling/phpGlobals.json new file mode 100644 index 000000000..cbb8bb501 --- /dev/null +++ b/src/extensions/default/PhpTooling/phpGlobals.json @@ -0,0 +1,75 @@ +{ + "$GLOBALS": { + "description": "An associative array containing references to all variables which are currently defined in the global scope of the script. The variable names are the keys of the array.", + "type": "array" + }, + "$_SERVER": { + "description": "$_SERVER is an array containing information such as headers, paths, and script locations. The entries in this array are created by the web server. There is no guarantee that every web server will provide any of these; servers may omit some, or provide others not listed here. That said, a large number of these variables are accounted for in the CGI/1.1 specification, so you should be able to expect those.", + "type": "array" + }, + "$_GET": { + "description": "An associative array of variables passed to the current script via the URL parameters.", + "type": "array" + }, + "$_POST": { + "description": "An associative array of variables passed to the current script via the HTTP POST method.", + "type": "array" + }, + "$_FILES": { + "description": "An associative array of items uploaded to the current script via the HTTP POST method.", + "type": "array" + }, + "$_REQUEST": { + "description": "An associative array that by default contains the contents of $_GET, $_POST and $_COOKIE.", + "type": "array" + }, + "$_SESSION": { + "description": "An associative array containing session variables available to the current script. See the Session functions documentation for more information on how this is used.", + "type": "array" + }, + "$_ENV": { + "description": "An associative array of variables passed to the current script via the environment method. \r\n\r\nThese variables are imported into PHP\"s global namespace from the environment under which the PHP parser is running. Many are provided by the shell under which PHP is running and different systems are likely running different kinds of shells, a definitive list is impossible. Please see your shell\"s documentation for a list of defined environment variables. \r\n\r\nOther environment variables include the CGI variables, placed there regardless of whether PHP is running as a server module or CGI processor.", + "type": "array" + }, + "$_COOKIE": { + "description": "An associative array of variables passed to the current script via HTTP Cookies.", + "type": "array" + }, + "$php_errormsg": { + "description": "$php_errormsg is a variable containing the text of the last error message generated by PHP. This variable will only be available within the scope in which the error occurred, and only if the track_errors configuration option is turned on (it defaults to off).", + "type": "array" + }, + "$HTTP_RAW_POST_DATA": { + "description": "$HTTP_RAW_POST_DATA contains the raw POST data. See always_populate_raw_post_data", + "type": "array" + }, + "$http_response_header": { + "description": "The $http_response_header array is similar to the get_headers() function. When using the HTTP wrapper, $http_response_header will be populated with the HTTP response headers. $http_response_header will be created in the local scope.", + "type": "array" + }, + "$argc": { + "description": "Contains the number of arguments passed to the current script when running from the command line.", + "type": "array" + }, + "$argv": { + "description": "Contains an array of all the arguments passed to the script when running from the command line.", + "type": "array" + }, + "$this": { + "description": "Refers to the current object", + "type": "array" + }, + "parent": { + "description": "", + "type": "" + }, + "self": { + "description": "", + "type": "" + }, + "_destruct": { + "description": "", + "type": "" + } + +} \ No newline at end of file diff --git a/src/extensions/default/PhpTooling/unittest-files/mac/invalidphp b/src/extensions/default/PhpTooling/unittest-files/mac/invalidphp new file mode 100755 index 0000000000000000000000000000000000000000..487eecd1068df6db2a811eb09995043695cb53e8 GIT binary patch literal 10292 zcmeHNe{2)i9e<7k%UFg3r5)`U7)mM!st_jxl8$Zh$Zh9gLm;6=6#C*gN9@h9GyB{+ z+QQP3sJ)p;nW}b^s5GYik}7RWwQfSy6s4nDBJD>5n&_lfg-UCBDWzhQ77#0VpYOY~ zeYvC)DgXM(?|py0_j%vt$azXlccls{J6`2y4ZOo<07H@673*hD3l8WZjI&{wCF`nx0?MlRWpPF2Nx7PG8?-t2+ zyw&->Ksk?v{vV#l#LLHxWGfzxcZ~(Fc%26Pd-q7K{`@uOw#5xHrR^04B^-0<2*oB` z{&mE;8KWu{pq-%k6mjF4!UQ(m&luICxuh6oAm0tzQO(#TC_e+f4cVHgF6FzdN*h15 znK3{uNLbe7601bsmQE$N#iCu?y8C0@kSTp0nf&>K@uw{ETV*20Nsm+-5}Qon51 zg>rlS_WqQr^wxgWY(l8)0pu#=2nJ2G#*`_ZrBsSQDFUSkEF}U5W$j(x!|n2k531zs zm)7GpCXY0J3r|q5Yy*rwf$!AL)&?ffbSj|D=sT&`Y@-{4B9*>T$i}GL1V*EJ zMtVaSx5MZfm)`JcLz@Xr&JIij9k+Z>zB>lVcjBfypxyAhS}(M^#({n-p#3YTUD8ic z)!~Ws=SU5v@+cNzMZW%NT{X8++KM)?zqrM})qrS&{{ODP~i07343apn` z!x=qJ9E1LHrC}V?e+U)L$gc>32A{#SUDQuP)PGFXe~DP@&yf|=&@AYrUJdHn(8|~% z@qOK|{hi`F{HAN@EW8hB*X5e40Zkh7XXUk$_ljKeYQVK`HmJ?`YvzK(bH}TxW9?!< z`*%PaI1|*~leE9&+BzleouKwnAUkj-(0n!ho#615;{n$r6RsL*LY|bS0Zq%~93n6g z&_2|E19?uGaL6O+S7n!!o;D)ZDUjdKO&cSg5NkBp{9$@au;#tsaPIh;eD5=u0CXE@ zzMftwXQeTDvW0HX>~fj+&4tl5mf(c`Wz?Y1tQT}rr{!E+e{DKFDCvF_qq*vPu(EU2 zYejjVDA$Q{y(rzH+$74)qI^J<9#L))<%6QE6=j_$>ro=eBVTw43(VJ^tEOvVG;$A= zvopC|F0z}rw}_*M^4asmogr?DI45RucAU5t;$9~1d&Ip++-t<0CT=-UXP+l-6LBYr z3laAeaYMvqhoj#IxQ9+5A`;ng}fg^7vhSK+u>9L6} z0mri_s*pv3S4g>2KYwy?kU3v)+y{xCsMf&X*bjzk-b+FV%x2_|kR8J)jvx!mJ75TX zHC2OWLRdkNz6Y8&d@o^UK$|u5WhN*LioM zc&Gut`WiQ$Dj8dcOw?^siV5b2ojbH_e-(VC=Tlm#G#m6EloH9Lr~iC|80itvOK`aY ze_WifRkCv65(+CDLs115Ym&NQ8(A66!)>Q>U{k}4`TzAPlb$r`^CmrQ(ictovPs8H zI%U%HCVk7KGbX)i(irY5hz9A zV0)4X1=y#9N@s3y` z9EvH;>6F5cg~Ey|9oXAm$BhnIvT0uBrf4F?yFw{tM~|YSq4YQbwpa(^t%p4thBNfW z+aKQB&b{7nB$VW8G89!)7>op6b0|g~tBE8jq#YbDv!kli8zw;c&H-D!hHq{2pc)L{ z5^zol^)Bw5)X;~S5Plq5dgI1pv2-e8^xe7i<~veKTut`x_DToZ8#u)x@`*w6A?ULr znA4rNWvsTs>^5LUrW6%3-Q5*cc?h2xgcI>p6bp+d6*ZDfJYkG*(6MOy#&AptC8ckY z|IZjz+SO)NN%ltLAypBJW>n~TI1}PsN>4P-u^7S;YVo6@MWLQeJr=Zs zh7wMz3cmRW(Q4n=8;ZtRH66P-*>wJ0_w-$3j!R{y#bF#Q;#AJ%Fb*N diff --git a/src/extensions/default/PhpTooling/unittest-files/test/test3.php b/src/extensions/default/PhpTooling/unittest-files/test/test3.php new file mode 100644 index 000000000..70981b54d --- /dev/null +++ b/src/extensions/default/PhpTooling/unittest-files/test/test3.php @@ -0,0 +1,10 @@ +Y!bA+ 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�?tQJk0xc0qi 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 literal 0 HcmV?d00001 diff --git a/src/extensions/default/PhpTooling/unittests.js b/src/extensions/default/PhpTooling/unittests.js new file mode 100644 index 000000000..722acc5b9 --- /dev/null +++ b/src/extensions/default/PhpTooling/unittests.js @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2019 - present Adobe. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ +/*global describe, runs, beforeEach, it, expect, waitsFor, waitsForDone, beforeFirst, afterLast */ +define(function (require, exports, module) { + 'use strict'; + + var SpecRunnerUtils = brackets.getModule("spec/SpecRunnerUtils"), + Strings = brackets.getModule("strings"), + FileUtils = brackets.getModule("file/FileUtils"), + StringUtils = brackets.getModule("utils/StringUtils"); + + var extensionRequire, + phpToolingExtension, + testWindow, + $, + PreferencesManager, + CodeInspection, + DefaultProviders, + CodeHintsProvider, + EditorManager, + testEditor, + testFolder = FileUtils.getNativeModuleDirectoryPath(module) + "/unittest-files/", + testFile1 = "test1.php", + testFile2 = "test2.php"; + + describe("PhpTooling", function () { + + beforeFirst(function () { + + // Create a new window that will be shared by ALL tests in this spec. + SpecRunnerUtils.createTestWindowAndRun(this, function (w) { + testWindow = w; + $ = testWindow.$; + var brackets = testWindow.brackets; + extensionRequire = brackets.test.ExtensionLoader.getRequireContextForExtension("PhpTooling"); + phpToolingExtension = extensionRequire("main"); + }); + }); + + afterLast(function () { + waitsForDone(phpToolingExtension.getClient().stop(), "stoping php server"); + SpecRunnerUtils.closeTestWindow(); + testWindow = null; + }); + + + beforeEach(function () { + EditorManager = testWindow.brackets.test.EditorManager; + PreferencesManager = testWindow.brackets.test.PreferencesManager; + CodeInspection = testWindow.brackets.test.CodeInspection; + CodeInspection.toggleEnabled(true); + DefaultProviders = testWindow.brackets.getModule("languageTools/DefaultProviders"); + CodeHintsProvider = extensionRequire("CodeHintsProvider"); + }); + + /** + * Does a busy wait for a given number of milliseconds + * @param {Number} milliSeconds - number of milliSeconds to wait + */ + function waitForMilliSeconds(milliSeconds) { + var flag = false; + + setTimeout(function () { + flag = true; + }, milliSeconds); + + waitsFor(function () { + return flag; + }, "This should not fail. Please check the timeout values.", + milliSeconds + 10); // We give 10 milliSeconds as grace period + } + + /** + * Check the presence of a Button in Error Prompt + * @param {String} btnId - "CANCEL" or "OPEN" + */ + function checkPopUpButton(clickbtnId) { + var doc = $(testWindow.document), + errorPopUp = doc.find(".error-dialog.instance"), + btn = errorPopUp.find('.dialog-button'); + + // Test if the update bar button has been displayed. + expect(btn.length).toBe(2); + if (clickbtnId) { + clickButton(clickbtnId); + } + } + + /** + * Check the presence of a Button in Error Prompt + * @param {String} btnId - Button OPEN or Cancel Button + */ + function clickButton(btnId) { + var doc = $(testWindow.document), + errorPopUp = doc.find(".error-dialog.instance"), + btn = errorPopUp.find('.dialog-button'), + openBtn, + cancelBtn, + clickBtn; + if (btn[0].classList.contains("primary")) { + openBtn = btn[0]; + cancelBtn = btn[1]; + } + + if (btn[1].classList.contains("primary")) { + openBtn = btn[1]; + cancelBtn = btn[0]; + } + clickBtn = cancelBtn; + + if(btnId === "OPEN") { + clickBtn = openBtn; + } + + if(clickBtn) { + clickBtn.click(); + waitForMilliSeconds(3000); + runs(function() { + expect(doc.find(".error-dialog.instance").length).toBe(0); + }); + } + } + + /** + * Check the presence of Error Prompt String on Brackets Window + * @param {String} title - Title String Which will be matched with Update Bar heading. + * @param {String} description - description String Which will be matched with Update Bar description. + */ + function checkPopUpString(title, titleDescription) { + var doc = $(testWindow.document), + errorPopUp = doc.find(".error-dialog.instance"), + heading = errorPopUp.find('.dialog-title'), + description = errorPopUp.find('.dialog-message'); + + // Test if the update bar has been displayed. + //expect(errorPopUp.length).toBe(1); + if (title) { + expect(heading.text()).toBe(title); + } + if (titleDescription) { + expect(description.text()).toBe(titleDescription); + } + } + + function toggleDiagnosisResults(visible) { + var doc = $(testWindow.document), + problemsPanel = doc.find("#problems-panel"), + statusInspection = $("#status-inspection"); + statusInspection.triggerHandler("click"); + expect(problemsPanel.is(":visible")).toBe(visible); + } + + /** + * Wait for the editor to change positions, such as after a jump to + * definition has been triggered. Will timeout after 3 seconds + * + * @param {{line:number, ch:number}} oldLocation - the original line/col + * @param {Function} callback - the callback to apply once the editor has changed position + */ + function _waitForJump(jumpPromise, callback) { + var cursor = null, + complete = false; + + jumpPromise.done(function () { + complete = true; + }); + + waitsFor(function () { + var activeEditor = EditorManager.getActiveEditor(); + cursor = activeEditor.getCursorPos(); + return complete; + }, "Expected jump did not occur", 3000); + + runs(function () { callback(cursor); }); + } + + /* + * Expect a given list of hints to be present in a given hint + * response object + * + * @param {Object + jQuery.Deferred} hintObj - a hint response object, + * possibly deferred + * @param {Array.} expectedHints - a list of hints that should be + * present in the hint response + */ + function expecthintsPresent(expectedHints) { + var hintObj = (new CodeHintsProvider.CodeHintsProvider(phpToolingExtension.getClient())).getHints(null); + _waitForHints(hintObj, function (hintList) { + expect(hintList).toBeTruthy(); + expectedHints.forEach(function (expectedHint) { + expect(_indexOf(hintList, expectedHint)).not.toBe(-1); + }); + }); + } + + /* + * Return the index at which hint occurs in hintList + * + * @param {Array.} hintList - the list of hints + * @param {string} hint - the hint to search for + * @return {number} - the index into hintList at which the hint occurs, + * or -1 if it does not + */ + function _indexOf(hintList, hint) { + var index = -1, + counter = 0; + + for (counter; counter < hintList.length; counter++) { + if (hintList[counter].data("token").label === hint) { + index = counter; + break; + } + } + return index; + } + + /* + * Wait for a hint response object to resolve, then apply a callback + * to the result + * + * @param {Object + jQuery.Deferred} hintObj - a hint response object, + * possibly deferred + * @param {Function} callback - the callback to apply to the resolved + * hint response object + */ + function _waitForHints(hintObj, callback) { + var complete = false, + hintList = null; + + if (hintObj.hasOwnProperty("hints")) { + complete = true; + hintList = hintObj.hints; + } else { + hintObj.done(function (obj) { + complete = true; + hintList = obj.hints; + }); + } + + waitsFor(function () { + return complete; + }, "Expected hints did not resolve", 3000); + + runs(function () { callback(hintList); }); + } + + /** + * Show a function hint based on the code at the cursor. Verify the + * hint matches the passed in value. + * + * @param {Array<{name: string, type: string, isOptional: boolean}>} + * expectedParams - array of records, where each element of the array + * describes a function parameter. If null, then no hint is expected. + * @param {number} expectedParameter - the parameter at cursor. + */ + function expectParameterHint(expectedParams, expectedParameter) { + var requestStatus = null; + var request, + complete = false; + runs(function () { + request = (new DefaultProviders.ParameterHintsProvider(phpToolingExtension.getClient())) + .getParameterHints(); + request.done(function (status) { + complete = true; + requestStatus = status; + }).fail(function(){ + complete = true; + }); + }); + + waitsFor(function () { + return complete; + }, "Expected Parameter hints did not resolve", 3000); + + if (expectedParams === null) { + expect(requestStatus).toBe(null); + return; + } + + function expectHint(hint) { + var params = hint.parameters, + n = params.length, + i; + + // compare params to expected params + expect(params.length).toBe(expectedParams.length); + expect(hint.currentIndex).toBe(expectedParameter); + + for (i = 0; i < n; i++) { + expect(params[i].label).toBe(expectedParams[i]); + } + + } + runs(function() { + expectHint(requestStatus); + }); + } + + /** + * Trigger a jump to definition, and verify that the editor jumped to + * the expected location. The new location is the variable definition + * or function definition of the variable or function at the current + * cursor location. Jumping to the new location will cause a new editor + * to be opened or open an existing editor. + * + * @param {{line:number, ch:number, file:string}} expectedLocation - the + * line, column, and optionally the new file the editor should jump to. If the + * editor is expected to stay in the same file, then file may be omitted. + */ + function editorJumped(expectedLocation) { + var jumpPromise = (new DefaultProviders.JumpToDefProvider(phpToolingExtension.getClient())).doJumpToDef(); + + _waitForJump(jumpPromise, function (newCursor) { + expect(newCursor.line).toBe(expectedLocation.line); + expect(newCursor.ch).toBe(expectedLocation.ch); + if (expectedLocation.file) { + var activeEditor = EditorManager.getActiveEditor(); + expect(activeEditor.document.file.name).toBe(expectedLocation.file); + } + }); + + } + + /** + * Check the presence of Error Prompt on Brackets Window + */ + function checkErrorPopUp() { + var doc = $(testWindow.document), + errorPopUp = doc.find(".error-dialog.instance"), + errorPopUpHeader = errorPopUp.find(".modal-header"), + errorPopUpBody = errorPopUp.find(".modal-body"), + errorPopUpFooter = errorPopUp.find(".modal-footer"), + errorPopUpPresent = false; + + runs(function () { + expect(errorPopUp.length).toBe(1); + expect(errorPopUpHeader).not.toBeNull(); + expect(errorPopUpBody).not.toBeNull(); + expect(errorPopUpFooter).not.toBeNull(); + }); + + if (errorPopUp && errorPopUp.length > 0) { + errorPopUpPresent = true; + } + return errorPopUpPresent; + } + + it("phpTooling Exiension should be loaded Successfully", function () { + waitForMilliSeconds(5000); + runs(function () { + expect(phpToolingExtension).not.toBeNull(); + }); + }); + + it("should attempt to start php server and fail due to lower version of php", function () { + var phpExecutable = testWindow.brackets.platform === "mac" ? "/mac/invalidphp" : "/win/invalidphp"; + PreferencesManager.set("php", { + "executablePath": testFolder + phpExecutable + }, { + locations: {scope: "session"} + }); + waitForMilliSeconds(5000); + runs(function () { + checkErrorPopUp(); + checkPopUpString(Strings.PHP_SERVER_ERROR_TITLE, + StringUtils.format(Strings.PHP_UNSUPPORTED_VERSION, "5.6.30")); + checkPopUpButton("CANCEL"); + }); + }); + + it("should attempt to start php server and fail due to invaild executable", function () { + PreferencesManager.set("php", {"executablePath": "/invalidPath/php"}, {locations: {scope: "session"}}); + waitForMilliSeconds(5000); + runs(function () { + checkErrorPopUp(); + checkPopUpString(Strings.PHP_SERVER_ERROR_TITLE, Strings.PHP_EXECUTABLE_NOT_FOUND); + checkPopUpButton("CANCEL"); + }); + }); + + it("should attempt to start php server and fail due to invaild memory limit in prefs settings", function () { + PreferencesManager.set("php", {"memoryLimit": "invalidValue"}, {locations: {scope: "session"}}); + waitForMilliSeconds(5000); + runs(function () { + checkErrorPopUp(); + checkPopUpString(Strings.PHP_SERVER_ERROR_TITLE, Strings.PHP_SERVER_MEMORY_LIMIT_INVALID); + checkPopUpButton("CANCEL"); + }); + + runs(function () { + SpecRunnerUtils.loadProjectInTestWindow(testFolder + "test"); + }); + }); + + it("should attempt to start php server and success", function () { + PreferencesManager.set("php", {"memoryLimit": "4095M"}, {locations: {scope: "session"}}); + + waitsForDone(SpecRunnerUtils.openProjectFiles([testFile1]), "open test file: " + testFile1); + waitForMilliSeconds(5000); + runs(function () { + toggleDiagnosisResults(false); + toggleDiagnosisResults(true); + }); + }); + + it("should filter hints by query", function () { + waitsForDone(SpecRunnerUtils.openProjectFiles([testFile2]), "open test file: " + testFile2); + runs(function() { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos({ line: 15, ch: 3 }); + expecthintsPresent(["$A11", "$A12", "$A13"]); + }); + }); + + it("should show inbuilt functions in hints", function () { + runs(function() { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos({ line: 17, ch: 2 }); + expecthintsPresent(["fopen", "for", "foreach"]); + }); + }); + + it("should show static global variables in hints", function () { + runs(function() { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos({ line: 20, ch: 1 }); + expecthintsPresent(["$_COOKIE", "$_ENV"]); + }); + }); + + it("should not show parameter hints", function () { + runs(function() { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos({ line: 25, ch: 5 }); + expectParameterHint(null); + }); + }); + + it("should show no parameter as a hint", function () { + runs(function() { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos({ line: 27, ch: 19 }); + expectParameterHint([], 0); + }); + }); + + it("should show parameters hints", function () { + runs(function() { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos({ line: 26, ch: 9 }); + expectParameterHint([ + "string $filename", + "string $mode", + "bool $use_include_path = null", + "resource $context = null"], 1); + }); + }); + + it("should jump to earlier defined variable", function () { + var start = { line: 4, ch: 2 }; + + runs(function () { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos(start); + editorJumped({line: 2, ch: 0}); + }); + }); + + it("should jump to class declared in other module file", function () { + var start = { line: 9, ch: 11 }; + + runs(function () { + testEditor = EditorManager.getActiveEditor(); + testEditor.setCursorPos(start); + editorJumped({line: 4, ch: 0, file: "test3.php"}); + }); + }); + }); +}); diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 074c462c4..088b6e024 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -872,11 +872,22 @@ define({ "DESCRIPTION_AUTO_UPDATE" : "Enable/disable Brackets Auto-update", "AUTOUPDATE_ERROR" : "Error!", "AUTOUPDATE_IN_PROGRESS" : "An update is already in progress.", - + "NUMBER_WITH_PERCENTAGE" : "{0}%", // Strings for Related Files "CMD_FIND_RELATED_FILES" : "Find Related Files", - + + ///String for Php Tooling Extensions + "PHP_VERSION_INVALID" : "Error parsing PHP version. Please check the output of the “php –version” command.", + "PHP_UNSUPPORTED_VERSION" : "Install PHP7 runtime for enabling PHP-related tooling such as Code Hints, Parameter Hints, Jump To Definition and more. Version found: {0}", + "PHP_EXECUTABLE_NOT_FOUND" : "PHP runtime not found. Install PHP7 runtime and set the path to system PATH or executablePath in php Preferences appropriately.", + "PHP_PROCESS_SPAWN_ERROR" : "Error code {0} encountered while starting the PHP process.", + "PHP_SERVER_ERROR_TITLE" : "Error", + "PHP_SERVER_MEMORY_LIMIT_INVALID" : "The memory limit you provided is invalid. Review the PHP preferences to set the correct value.", + "DESCRIPTION_PHP_TOOLING_CONFIGURATION" : "PHP Tooling default configuration settings", + "OPEN_PREFERENNCES" : "Open Preferences", + "PHP_DIAGNOSTICS" : "Diagnostics", + //Strings for LanguageTools Preferences LANGUAGE_TOOLS_PREFERENCES : "Preferences for Language Tools" });