├── .editorconfig
├── .gitignore
├── .travis.yml
├── .vscode
├── launch.json
└── tasks.json
├── .vscodeignore
├── CHANGELOG.md
├── LICENCE
├── README.md
├── client
└── client.ts
├── images
├── vscode-phpmd.png
└── vscode-phpmd.svg
├── mocha.opts
├── package.json
├── phpmd
└── phpmd.phar
├── server
├── Server.ts
├── controller
│ └── PhpmdController.ts
├── factory
│ ├── BuildDiagnosticsTaskFactory.ts
│ ├── ClientConnectionNotifierFactory.ts
│ ├── ExecuteProcessTaskFactory.ts
│ ├── IFactory.ts
│ ├── ILoggerFactory.ts
│ ├── INotifierFactory.ts
│ ├── NullLoggerFactory.ts
│ ├── NullNotifierFactory.ts
│ ├── ParseTaskFactory.ts
│ ├── PhpmdControllerFactory.ts
│ ├── PipelineFactory.ts
│ ├── PipelinePayloadFactory.ts
│ ├── RemoteConsoleLoggerFactory.ts
│ └── TestFileTaskFactory.ts
├── init.ts
├── model
│ ├── IPhpmdEnvironmentModel.ts
│ ├── IPhpmdSettingsModel.ts
│ ├── PipelineErrorModel.ts
│ ├── PipelinePayloadModel.ts
│ └── pmd
│ │ ├── IPmd.ts
│ │ ├── IPmdFileData.ts
│ │ ├── IPmdFileMetaData.ts
│ │ ├── IPmdMetaData.ts
│ │ ├── IPmdViolation.ts
│ │ ├── IPmdViolationMetaData.ts
│ │ └── index.ts
└── service
│ ├── PhpmdCommandBuilder.ts
│ ├── PhpmdService.ts
│ ├── logger
│ ├── ILogger.ts
│ ├── NullLogger.ts
│ └── RemoteConsoleLogger.ts
│ ├── notifier
│ ├── ClientConnectionNotifier.ts
│ ├── INotifier.ts
│ └── NullNotifier.ts
│ └── pipeline
│ ├── BuildDiagnosticsStrategy.ts
│ ├── ExecuteProcessStrategy.ts
│ ├── ParseStrategy.ts
│ └── TestFileStrategy.ts
├── tests
├── ServerTest.ts
├── controller
│ └── PhpmdControllerTest.ts
├── factory
│ ├── BuildDiagnosticsTaskFactoryTest.ts
│ ├── ClientConnectionNotifierFactoryTest.ts
│ ├── ExecuteProcessTaskFactoryTest.ts
│ ├── NullLoggerFactoryTest.ts
│ ├── NullNotifierFactoryTest.ts
│ ├── ParseTaskFactoryTest.ts
│ ├── PhpmdControllerFactoryTest.ts
│ ├── PipelineFactoryTest.ts
│ ├── PipelinePayloadFactoryTest.ts
│ ├── RemoteConsoleLoggerFactoryTest.ts
│ └── TestFileTaskFactoryTest.ts
├── model
│ └── PipelinePayloadModelTest.ts
└── service
│ ├── PhpmdCommandBuilderTest.ts
│ ├── PhpmdServiceTest.ts
│ ├── logger
│ ├── NullLoggerTest.ts
│ └── RemoteConsoleLoggerTest.ts
│ ├── notifier
│ ├── ClientConnectionNotifierTest.ts
│ └── NullNotifierTest.ts
│ └── pipeline
│ ├── BuildDiagnosticsStrategyTest.ts
│ ├── ExecuteProcessStrategyTest.ts
│ ├── ParseStrategyTest.ts
│ └── TestFileStrategyTest.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [{src,scripts}/**.{ts,json,js}]
4 | end_of_line = crlf
5 | charset = utf-8
6 | trim_trailing_whitespace = true
7 | insert_final_newline = true
8 | indent_style = space
9 | indent_size = 4
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | node_modules
3 | client/server
4 | .nyc_output
5 | coverage
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '8'
4 | before_install:
5 | - sudo apt-key adv --fetch-keys http://dl.yarnpkg.com/debian/pubkey.gpg
6 | - echo "deb http://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
7 | - sudo apt-get update -qq
8 | - sudo apt-get install -y -qq yarn=1.19.2-1
9 | cache:
10 | yarn: true
11 | script:
12 | - yarn install
13 | - npm test
14 | - npm run report-coverage
15 | after_success:
16 | - cat ./coverage/coverage-final.json | ./node_modules/.bin/codecov
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.1.0",
6 | "configurations": [
7 | {
8 | "name": "Launch Language Provider",
9 | "type": "extensionHost",
10 | "request": "launch",
11 | "runtimeExecutable": "${execPath}",
12 | "args": [
13 | "--extensionDevelopmentPath=${workspaceRoot}"
14 | ],
15 | "preLaunchTask": "build",
16 | "stopOnEntry": false,
17 | "sourceMaps": true,
18 | "outFiles": [
19 | "${workspaceRoot}/out/client/**/*.js"
20 | ]
21 | },
22 | {
23 | "name": "Attach Language Provider Server",
24 | "type": "node",
25 | "request": "attach",
26 | "protocol": "inspector",
27 | "sourceMaps": true,
28 | "outFiles": [
29 | "${workspaceRoot}/out/server/**/*.js"
30 | ],
31 | "timeout": 50000 // Allow for build task to finish
32 | },
33 | {
34 | "type": "node",
35 | "request": "launch",
36 | "name": "Launch unit tests",
37 | "cwd": "${workspaceRoot}",
38 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/mocha",
39 | "runtimeArgs": [
40 | "--opts", "mocha.opts"
41 | ],
42 | "stopOnEntry": false,
43 | "sourceMaps": true
44 | }
45 | ],
46 | "compounds": [
47 | {
48 | "name": "Launch Language Provider & Attach to Server",
49 | "configurations": [
50 | "Launch Language Provider",
51 | "Attach Language Provider Server"
52 | ]
53 | }
54 | ]
55 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=733558
3 | // for the documentation about the tasks.json format
4 | "version": "2.0.0",
5 | "tasks": [
6 | {
7 | "label": "build",
8 | "type": "npm",
9 | "script": "compile",
10 | "problemMatcher": []
11 | }
12 | ]
13 | }
--------------------------------------------------------------------------------
/.vscodeignore:
--------------------------------------------------------------------------------
1 | **/*.ts
2 | **/tsconfig.json
3 | .nyc_output/**
4 | .vscode/**
5 | coverage/**
6 | .editorconfig
7 | .gitignore
8 | mocha.opts
9 | tslint.json
10 | .travis.yml
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Version 1.3.0
2 |
3 | * Added encoding of xml special chars in test filename (issue 61)
4 | * Fixed remote code execution vulnerability
5 | * Updated package dependencies
6 | * Updated integrated phpmd.phar to version 2.9.1
7 |
8 | # Version 1.2.0
9 |
10 | * Added variable replacement in phpmd command (workspaceFolder and os homedir)
11 | * Small spelling fixes
12 | * Updated package dependencies
13 | * Added trimming of diagnostic messages (issue 48)
14 |
15 | # Version 1.1.0
16 |
17 | * Added clearing of diagnostics on file close (issue #36)
18 | * Added enabled setting (issue #37)
19 |
20 | # Version 1.0.4
21 |
22 | * Fix issue #30
23 |
24 | # Version 1.0.3
25 |
26 | * Fix issue #23
27 |
28 | # Version 1.0.2
29 |
30 | * Fix issue #18
31 | * Fix issue #19
32 |
33 | # Version 1.0.1
34 |
35 | * Fix issue #12
36 | * Fix issue #14
37 |
38 | # Version 1.0.0
39 |
40 | * Added jsdoc comments to all "things"
41 | * Improved logging
42 | * Added a changelog
43 | * Added a licence
44 | * Completed the README
45 | * Submitted to the VSCode marketplace, yay!
46 |
47 | # Version 0.5.0
48 |
49 | * Ship PHP mess detector PHAR with extension
50 |
51 | # Version 0.4.0
52 |
53 | * Added merging of defaults and user settings
54 | * Added PHP mess detector availability checks
55 | * Added PHP mess detector config file support
56 |
57 | # Version 0.3.0
58 |
59 | * Added logging to client console
60 | * Client-server connection setup refactoring
61 |
62 | # Version 0.2.0
63 |
64 | * Added unit tests
65 |
66 | # Version 0.1.0
67 |
68 | * Initial version with PHP mess detector functionality
69 |
--------------------------------------------------------------------------------
/LICENCE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Sandhjé Bouw
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # VSCode PHP Mess Detector
2 |
3 | [](https://travis-ci.org/sandhje/vscode-phpmd)
4 | [](https://codecov.io/gh/sandhje/vscode-phpmd)
5 | [](https://gitter.im/sandhje/vscode-phpmd?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
6 |
7 | ## Features
8 |
9 | * Analyze your PHP source code on save with PHP mess detector
10 | * No additional setup required if PHP is installed on your system
11 | * Customize which PHPMD rules are used
12 | * Supports custom PHPMD ruleset files
13 |
14 | ## Installation
15 |
16 | To install the extension: Press `F1`, type `ext install vscode-phpmd`
17 |
18 | ### Using the built-in PHPMD PHAR
19 |
20 | To use the built-in PHP mess detector you need to have PHP in your PATH. To test, open a shell or command window and type `php -v`, this should return "PHP x.x.x ...". If PHP is in your PATH you can directly use this extension, no further setup is required.
21 |
22 | ### Using a custom PHPMD PHAR or executable
23 |
24 | If you want to customize the default PHP mess detector command, e.g. you have PHP mess detector globally installed through composer or have PHP available on a different location, you can customize the command with the `command` setting.
25 |
26 | ## Configuration
27 |
28 | The following configuration options are available:
29 |
30 | ### phpmd.command:
31 | Customize the PHP mess detector command. If left empty the built-in PHPMD PHAR archive will be executed and PHP needs to be available on your PATH. If you want to use a different PHPMD PHAR you can customize the command here.
32 |
33 | #### Examples:
34 | To use PHPMD installed globally with composer on a windows machine set this setting to:
35 | ```
36 | "phpmd.command": "C:/Users/{USER}/AppData/Roaming/Composer/vendor/bin/phpmd.bat"
37 | ```
38 |
39 | Or to use your own PHPMD PHAR on a custom location:
40 | ```
41 | "phpmd.command": "php C:/path/to/phpmd.phar"`
42 | ```
43 | ***Security fix in version 1.3.0:*** *Before version 1.3.0 it was possible to set `phpmd.command` through workspace settings. This unfortunately opened possibilities for a remote code execution attack. As such, since version 1.3.0, this setting is disabled at workspace level to address this issue. I do understand though that some users used this workspace setting in their day to day work therefore it is still possible to override the phpmd.command setting through workspace settings via the phpmd.unsafeCommand setting. This setting is disabled by default, use at your own risk, see the explanation at the phpmd.unsafeCommandEnabled setting.*
44 |
45 | ### phpmd.unsafeCommand:
46 | Customize the PHP mess detector command from workspace settings. This setting is ignored by default if `phpmd.unsafeCommandEnabled` is not set to `true`.
47 |
48 | *Warning:* Please be aware that using this setting opens a possibility for a remote code execution attack. See the explanation at the `phpmd.unsafeCommandEnabled` setting.
49 |
50 | ### phpmd.unsafeCommandEnabled:
51 | Enable customizing the PHP mess detector command from workspace settings. Default is `false`. This setting can't be set in workspace settings.
52 |
53 | *Warning:* Please be aware that enabling this setting opens a possibility for a remote code execution attack. When opening a folder from an untrusted source, the opened folder could have a .vscode folder with a settings.json file defining the `phpmd.unsafeCommand` setting. The command configured there will be executed as soon as a php file is opened in the workspace. The author of the untrusted code could execute any command on your system through this setting, resulting in your system possibly being compromised. Use at your own risk.
54 |
55 | ### phpmd.rules:
56 |
57 | Customize the PHPMD ruleset files used. This option can also take the path to a custom [PHPMD ruleset file](https://phpmd.org/documentation/creating-a-ruleset.html). Use VS Code's workspace settings to control the rules or ruleset files per workspace. When setting a path to a ruleset file, and the path starts with "~/" this will be replaced with the OS homedir. The string "${workspaceFolder}" in a path to a ruleset file will be replaced with an absolute path to the folder in the workspace which relates to the file that is being validated. Refer to [PHPMD's documentation](https://phpmd.org/documentation/index.html) for more information on the ruleset parameter.
58 |
59 | #### Examples:
60 | To use only the cleancode ruleset and skip all the others:
61 | ```
62 | "phpmd.rules": "cleancode"
63 | ```
64 |
65 | Pass a comma seperated list of rulesets:
66 | ```
67 | "phpmd.rules": "cleancode,codesize"
68 | ```
69 |
70 | Pass the path to a ruleset file:
71 | ```
72 | "phpmd.rules": "C:/path/to/phpmd_config.xml"
73 | ```
74 |
75 | Pass the path to a ruleset file located in the home directory:
76 | ```
77 | "phpmd.rules": "~/phpmd_config.xml"
78 | ```
79 |
80 | Pass the path to a ruleset file located in the workspace folder:
81 | ```
82 | "phpmd.rules": "${workspaceFolder}/phpmd_config.xml"
83 | ```
84 |
85 | ### phpmd.verbose:
86 | Turn verbose logging on or off. All log entries can be viewed VS Code's output panel. Generally this can be turned off (default) unless you need to troubleshoot problems.
87 |
88 | #### Examples:
89 | To enable verbose logging:
90 | ```
91 | "phpmd.verbose": true
92 | ```
93 |
94 | ## System requirements
95 | * PHP_Depend >= 2.0.0
96 | * PHP >= 5.3.9
97 | * [PHP XML extension](https://www.php.net/manual/en/simplexml.installation.php)
98 |
99 | ## Troubleshooting
100 | * Turn on verbose logging through settings and check output
101 | * Ask a question on [gitter](https://gitter.im/sandhje/vscode-phpmd)
102 | * Found a bug? [file an issue](https://github.com/sandhje/vscode-phpmd/issues) (include the logs)
103 |
104 | ## Contributing
105 |
106 | If you found a bug or can help with adding a new feature to this extension you can submit any code through a pull request. The requirements for a pull request to be accepted are:
107 |
108 | * Add unit tests for all new code (code coverage must not drop)
109 | * Add JSDoc comments to all "things"
110 | * Make sure there are no TSLint violations (see tslint.json)
111 |
112 | Before contributing also make sure you are familiar with VSCode's [language server development](https://code.visualstudio.com/docs/extensions/example-language-server)
113 |
114 | Install all dependencies with [yarn](https://yarnpkg.com/lang/en/)
115 |
116 | ## History
117 |
118 | See client/CHANGELOG.md
119 |
120 | ## Acknowledgements
121 |
122 | * The people behind [PHPMD](https://phpmd.org/people-behind.html)
123 | * The Microsoft VSCode team for [VSCode](https://code.visualstudio.com/) and [vscode-languageserver-node](https://github.com/Microsoft/vscode-languageserver-node).
124 | * Quentin Dreyer for his OS homedir replacement solution (https://github.com/qkdreyer)
125 | * Shane Smith for his spelling fixes (https://github.com/shane-smith)
126 | * RyotaK for reporting the phpmd.command security issue (https://twitter.com/ryotkak)
127 |
--------------------------------------------------------------------------------
/client/client.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import * as path from 'path';
4 | import { workspace, ExtensionContext } from 'vscode';
5 | import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient';
6 |
7 | export function activate(context: ExtensionContext) {
8 |
9 | let serverModule = context.asAbsolutePath(path.join('out', 'server', 'init.js'));
10 |
11 | // Server debug options
12 | let debugOptions = { execArgv: ["--nolazy", "--inspect"] };
13 |
14 | // Server options
15 | let serverOptions: ServerOptions = {
16 | run : { module: serverModule, transport: TransportKind.ipc },
17 | debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions }
18 | }
19 |
20 | // Client options
21 | let clientOptions: LanguageClientOptions = {
22 | documentSelector: ['php'],
23 | synchronize: {
24 | configurationSection: 'phpmd'
25 | }
26 | }
27 |
28 | // Client launched in debug mode?
29 | let forceDebug = process.execArgv.some(value => value.indexOf("--inspect") >= 0);
30 |
31 | // Create and start the client
32 | let client = new LanguageClient('vscode-phpmd', 'PHP Mess Detector', serverOptions, clientOptions, forceDebug);
33 | let disposable = client.start();
34 |
35 | console.log("PHP Mess Detector server started");
36 |
37 | // Push the disposable to the context's subscriptions so that the
38 | // client can be deactivated on extension deactivation
39 | context.subscriptions.push(disposable);
40 | }
41 |
--------------------------------------------------------------------------------
/images/vscode-phpmd.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandhje/vscode-phpmd/3be2744556746cd432e2a8015a6c1635813617b6/images/vscode-phpmd.png
--------------------------------------------------------------------------------
/images/vscode-phpmd.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
140 |
--------------------------------------------------------------------------------
/mocha.opts:
--------------------------------------------------------------------------------
1 | --require ts-node/register
2 | --require source-map-support/register
3 | --full-trace
4 | --bail
5 | --inspect
6 | --colors
7 | tests/**/*.ts
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vscode-phpmd",
3 | "displayName": "PHP Mess Detector",
4 | "description": "VSCode plugin for PHP Mess Detector",
5 | "author": "Ecodes.io",
6 | "license": "MIT",
7 | "version": "1.3.0",
8 | "publisher": "ecodes",
9 | "engines": {
10 | "vscode": "^1.37.0"
11 | },
12 | "categories": [
13 | "Linters"
14 | ],
15 | "activationEvents": [
16 | "onLanguage:php"
17 | ],
18 | "keywords": [
19 | "php",
20 | "phpmd",
21 | "mess detector",
22 | "diagnostics",
23 | "linter"
24 | ],
25 | "bugs": {
26 | "url": "https://github.com/sandhje/vscode-phpmd/issues"
27 | },
28 | "homepage": "https://github.com/sandhje/vscode-phpmd",
29 | "repository": {
30 | "type": "git",
31 | "url": "https://github.com/sandhje/vscode-phpmd.git"
32 | },
33 | "icon": "images/vscode-phpmd.png",
34 | "main": "./out/client/client",
35 | "contributes": {
36 | "configuration": {
37 | "type": "object",
38 | "title": "PHP Mess Detector configuration",
39 | "properties": {
40 | "phpmd.enabled": {
41 | "type": "boolean",
42 | "default": true,
43 | "description": "Enable mess detector"
44 | },
45 | "phpmd.command": {
46 | "type": "string",
47 | "scope": "machine",
48 | "default": "",
49 | "description": "The phpmd command. Leave empty to use the shipped phpmd phar (local php executable is required)"
50 | },
51 | "phpmd.unsafeCommandEnabled": {
52 | "type": "boolean",
53 | "scope": "machine",
54 | "default": false,
55 | "description": "Enable overriding phpmd command from workspace. This potentially makes you vulnerable to a remote code execution attack, only enable this if you know what you're doing."
56 | },
57 | "phpmd.unsafeCommand": {
58 | "type": "string",
59 | "scope": "machine-overridable",
60 | "default": "",
61 | "description": "Override phpmd command from workspace. This potentially makes you vulnerable to a remote code execution attack, only use this if you know what you're doing."
62 | },
63 | "phpmd.rules": {
64 | "type": "string",
65 | "default": "cleancode,codesize,controversial,design,unusedcode,naming",
66 | "description": "Phpmd ruleset (comma separated list of rulesets or the location of a config file)"
67 | },
68 | "phpmd.verbose": {
69 | "type": "boolean",
70 | "default": false,
71 | "description": "Enable verbose logging"
72 | },
73 | "phpmd.clearOnClose": {
74 | "type": "boolean",
75 | "default": true,
76 | "description": "Clear diagnostics for a file upon closing it"
77 | }
78 | }
79 | }
80 | },
81 | "scripts": {
82 | "vscode:prepublish": "tsc -p ./",
83 | "test": "nyc mocha --opts mocha.opts",
84 | "report-coverage": "nyc report --reporter=json",
85 | "compile": "tsc -p .",
86 | "watch": "tsc --watch -p ."
87 | },
88 | "devDependencies": {
89 | "@types/chai": "^4.2.5",
90 | "@types/mocha": "^7.0.0",
91 | "@types/node": "^12.12.11",
92 | "@types/sinon": "^7.5.1",
93 | "@types/xml2js": "^0.4.5",
94 | "@types/vscode": "^1.37.0",
95 | "@testdeck/mocha": "^0.1.2",
96 | "chai": "^4.2.0",
97 | "codecov": "^3.8.1",
98 | "mocha": "^7.0.0",
99 | "nyc": "^15.0.0",
100 | "sinon": "^7.5.0",
101 | "source-map-support": "^0.5.16",
102 | "ts-node": "^8.5.2",
103 | "tslint": "^5.20.1",
104 | "typescript": "3.7.2"
105 | },
106 | "dependencies": {
107 | "@open-sourcerers/j-stillery": "^0.2.0",
108 | "vscode-languageclient": "^5.2.1",
109 | "vscode-languageserver": "^5.2.1",
110 | "vscode-uri": "^2.1.1",
111 | "xml2js": "^0.4.17"
112 | },
113 | "nyc": {
114 | "include": [
115 | "server/*.ts",
116 | "server/**/*.ts"
117 | ],
118 | "extension": [
119 | ".ts"
120 | ],
121 | "require": [
122 | "ts-node/register"
123 | ],
124 | "reporter": [
125 | "text-summary"
126 | ],
127 | "sourceMap": true,
128 | "instrument": true
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/phpmd/phpmd.phar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sandhje/vscode-phpmd/3be2744556746cd432e2a8015a6c1635813617b6/phpmd/phpmd.phar
--------------------------------------------------------------------------------
/server/Server.ts:
--------------------------------------------------------------------------------
1 | import * as Path from "path";
2 | import {
3 | createConnection, IConnection, InitializeResult, IPCMessageReader, IPCMessageWriter,
4 | TextDocument, TextDocumentIdentifier, TextDocuments, WorkspaceFolder
5 | } from "vscode-languageserver";
6 | import PhpmdController from "./controller/PhpmdController";
7 | import ClientConnectionNotifierFactory from "./factory/ClientConnectionNotifierFactory";
8 | import ILoggerFactory from "./factory/ILoggerFactory";
9 | import INotifierFactory from "./factory/INotifierFactory";
10 | import PhpmdControllerFactory from "./factory/PhpmdControllerFactory";
11 | import RemoteConsoleLoggerFactory from "./factory/RemoteConsoleLoggerFactory";
12 | import IPhpmdSettingsModel from "./model/IPhpmdSettingsModel";
13 | import ILogger from "./service/logger/ILogger";
14 | import NullLogger from "./service/logger/NullLogger";
15 | import INotifier from "./service/notifier/INotifier";
16 | import NullNotifier from "./service/notifier/NullNotifier";
17 | import IPhpmdEnvironmentModel from "./model/IPhpmdEnvironmentModel";
18 | import { homedir } from "os";
19 |
20 | /**
21 | * PHP mess detector language server
22 | *
23 | * @module vscode-phpmd/server
24 | * @author Sandhjé Bouw (sandhje@ecodes.io)
25 | */
26 | class Server {
27 | /**
28 | * VSCode server connection class
29 | *
30 | * @property {IConnection} connection
31 | */
32 | private connection: IConnection;
33 |
34 | /**
35 | * VSCode client workspace folders
36 | *
37 | * @property {WorkspaceFolder[]} workspaceFolders
38 | */
39 | private workspaceFolders: WorkspaceFolder[];
40 |
41 | /**
42 | * OS home directory
43 | *
44 | * @property {string} homeDir
45 | */
46 | private homeDir: string;
47 |
48 | /**
49 | * PHP mess detector controller class
50 | *
51 | * Contains the main API to run the PHPMD command and process its results.
52 | *
53 | * @property {PhpmdController} PhpmdController
54 | */
55 | private controller: PhpmdController;
56 |
57 | /**
58 | * PHP mess detector controller factory class
59 | *
60 | * Creates PHPMD controller instances.
61 | *
62 | * @property {PhpmdControllerFactory} controllerFactory
63 | */
64 | private controllerFactory: PhpmdControllerFactory;
65 |
66 | /**
67 | * VSCode document manager class
68 | *
69 | * API to access and manipulate documents open in the connected VSCode window.
70 | *
71 | * @property {TextDocuments} documentsManager
72 | */
73 | private documentsManager: TextDocuments;
74 |
75 | /**
76 | * Logger class
77 | *
78 | * Process log messages of various severity levels.
79 | *
80 | * @property {ILogger} logger
81 | */
82 | private logger: ILogger;
83 |
84 | /**
85 | * Logger factory class
86 | *
87 | * Creates logger instances.
88 | *
89 | * @property {ILoggerFactory} loggerFactory
90 | */
91 | private loggerFactory: ILoggerFactory;
92 |
93 | /**
94 | * Notifier class
95 | *
96 | * Send end-user friendly notifications.
97 | *
98 | * @property {INotifier} notifier
99 | */
100 | private notifier: INotifier;
101 |
102 | /**
103 | * Notifier factory class
104 | *
105 | * Creates notifier instances.
106 | *
107 | * @property {INotifierFactory} notifierFactory
108 | */
109 | private notifierFactory: INotifierFactory;
110 |
111 | /**
112 | * Connection setter
113 | *
114 | * Allows injection of connection for better testability.
115 | *
116 | * @param {IConnection} connection
117 | * @returns {void}
118 | */
119 | public setConnection(connection: IConnection): void {
120 | this.connection = connection;
121 | }
122 |
123 | /**
124 | * WorkspaceFolders setter
125 | *
126 | * Allows injection of workspaceFolders for better testability.
127 | *
128 | * @param {WorkspaceFolder[]} workspaceFolders
129 | * @returns {void}
130 | */
131 | public setWorkspaceFolders(workspaceFolders: WorkspaceFolder[]): void {
132 | this.workspaceFolders = workspaceFolders;
133 | }
134 |
135 | /**
136 | * HomeDir setter
137 | *
138 | * Allows injection of homedir for better testability.
139 | *
140 | * @param {string} HomeDir
141 | * @returns {void}
142 | */
143 | public setHomeDir(homeDir: string): void {
144 | this.homeDir = homeDir;
145 | }
146 |
147 | /**
148 | * ControllerFactory setter
149 | *
150 | * Allows injection of controllerFactory for better testability.
151 | *
152 | * @param {PhpmdControllerFactory} controllerFactory
153 | * @returns {void}
154 | */
155 | public setControllerFactory(controllerFactory: PhpmdControllerFactory): void {
156 | this.controllerFactory = controllerFactory;
157 | }
158 |
159 | /**
160 | * LoggerFactory setter
161 | *
162 | * Allows injection of loggerFactory for better testability.
163 | *
164 | * @param {ILoggerFactory} loggerFactory
165 | * @returns {void}
166 | */
167 | public setLoggerFactory(loggerFactory: ILoggerFactory): void {
168 | this.loggerFactory = loggerFactory;
169 | }
170 |
171 | /**
172 | * NotifierFactory setter
173 | *
174 | * Allows injection of notifierFactory for better testability.
175 | *
176 | * @param {INotifierFactory} notifierFactory
177 | * @returns {void}
178 | */
179 | public setNotifierFactory(notifierFactory: INotifierFactory): void {
180 | this.notifierFactory = notifierFactory;
181 | }
182 |
183 | /**
184 | * DocumentsManager setter
185 | *
186 | * Allows injection of documentsManager for better testability.
187 | *
188 | * @param {TextDocuments} documentsManager
189 | * @returns {void}
190 | */
191 | public setDocumentsManager(documentsManager: TextDocuments): void {
192 | this.documentsManager = documentsManager;
193 | }
194 |
195 | /**
196 | * Controller setter
197 | *
198 | * Allows injection of controller for better testability.
199 | *
200 | * @param {PhpmdController} controller
201 | * @returns {void}
202 | */
203 | public setController(controller: PhpmdController): void {
204 | this.controller = controller;
205 | }
206 |
207 | /**
208 | * Logger setter
209 | *
210 | * Allows injection of logger for better testability.
211 | *
212 | * @param {ILogger} logger
213 | * @returns {void}
214 | */
215 | public setLogger(logger: ILogger): void {
216 | this.logger = logger;
217 | }
218 |
219 | /**
220 | * Notifier setter
221 | *
222 | * Allows injection of notifier for better testability.
223 | *
224 | * @param {INotifier} notifier
225 | * @returns {void}
226 | */
227 | public setNotifier(notifier: INotifier): void {
228 | this.notifier = notifier;
229 | }
230 |
231 | /**
232 | * Server's main point of entry
233 | *
234 | * The main method sets up the connection starts the listening and registers relevant event listeners and
235 | * their handlers on the connection.
236 | *
237 | * @returns {void}
238 | */
239 | public main(): void {
240 | // Get VSCode documentManager and connection
241 | let documentsManager: TextDocuments = this.getDocumentsManager();
242 | let connection: IConnection = this.getConnection();
243 |
244 | // Create logger
245 | this.createLogger(connection);
246 |
247 | // Create notifier
248 | this.createNotifier(connection);
249 |
250 | // Manage documents for connection
251 | documentsManager.listen(connection);
252 |
253 | // The settings have changed. Is send on server activation as well.
254 | connection.onDidChangeConfiguration((change) => {
255 | this.getLogger().info("Configuration change triggerd, validating all open documents.");
256 |
257 | let settings = this.createSettings(change.settings.phpmd);
258 | let environment = this.createEnvironment();
259 | this.logger.setVerbose(settings.verbose);
260 |
261 | this.getLogger().info("Creating controller", true);
262 | this.createController(connection, settings, environment);
263 |
264 | // (Re)Validate any open text documents
265 | documentsManager.all().forEach((document: TextDocument) => {
266 | this.getLogger().info("Validating document " + document.uri, true);
267 | this.getController().validate(document).then((result: boolean) => {
268 | this.getLogger().info("Document validation after config change completed successfully");
269 | }, (err: Error) => {
270 | this.getLogger().error(
271 | "An error occured during document validation after config change with the following message: "
272 | + err.message
273 | );
274 | });
275 | });
276 | });
277 |
278 | // A php document was opened
279 | connection.onDidOpenTextDocument((parameters) => {
280 | this.getLogger().info("New document opened, starting validation.");
281 |
282 | let document: TextDocumentIdentifier = parameters.textDocument;
283 |
284 | this.getController().validate(document).then((result: boolean) => {
285 | this.getLogger().info("Document validation after open completed successfully");
286 | }, (err: Error) => {
287 | this.getLogger().error(
288 | "An error occured during document validation after open with the following message: " + err.message
289 | );
290 | });
291 | });
292 |
293 | // A php document was saved
294 | connection.onDidSaveTextDocument((parameters) => {
295 | this.getLogger().info("Document saved, starting validation.");
296 |
297 | let document: TextDocumentIdentifier = parameters.textDocument;
298 |
299 | this.getController().validate(document).then((result: boolean) => {
300 | this.getLogger().info("Document validation after save completed successfully");
301 | }, (err: Error) => {
302 | this.getLogger().error(
303 | "An error occured during document validation after save with the following message: " + err.message
304 | );
305 | });
306 | });
307 |
308 | // A php document was closed
309 | connection.onDidCloseTextDocument((parameters) => {
310 | this.getLogger().info("Document closed, clearing messages.");
311 |
312 | let document: TextDocumentIdentifier = parameters.textDocument;
313 |
314 | this.getController().clear(document);
315 | });
316 |
317 | // Set connection capabilities
318 | connection.onInitialize((params) => {
319 | this.getLogger().info("Language server connection initialized.");
320 |
321 | if (params && params.workspaceFolders) {
322 | this.getLogger().info(`Setting workspaceFolders ${JSON.stringify(params.workspaceFolders)}.`);
323 | this.setWorkspaceFolders(params.workspaceFolders)
324 | }
325 |
326 | return this.getInitializeResult();
327 | });
328 |
329 | // Listen on the connection
330 | connection.listen();
331 | }
332 |
333 | /**
334 | * Return the initializeResult object for the server's documentManager
335 | *
336 | * @returns {InitializeResult}
337 | */
338 | protected getInitializeResult(): InitializeResult {
339 | return {
340 | capabilities: {
341 | textDocumentSync: this.getDocumentsManager().syncKind,
342 | }
343 | };
344 | }
345 |
346 | /**
347 | * Get the VSCode connection or create one if no connection was set before
348 | *
349 | * @returns {IConnection}
350 | */
351 | protected getConnection(): IConnection {
352 | if (!this.connection) {
353 | this.connection = createConnection(new IPCMessageReader(process), new IPCMessageWriter(process));
354 | }
355 |
356 | return this.connection;
357 | }
358 |
359 | /**
360 | * Get the VSCode connection workspaceFolders
361 | *
362 | * @returns {WorkspaceFolder[]}
363 | */
364 | protected getWorkspaceFolders(): WorkspaceFolder[] {
365 | return this.workspaceFolders;
366 | }
367 |
368 | /**
369 | * Get the OS HomeDir
370 | *
371 | * @returns {string}
372 | */
373 | protected getHomeDir(): string {
374 | if (!this.homeDir) {
375 | this.homeDir = homedir();
376 | }
377 |
378 | return this.homeDir;
379 | }
380 |
381 | /**
382 | * Get the VSCode documentsManager or create on if no documentsManager was set before
383 | *
384 | * @returns {TextDocuments}
385 | */
386 | protected getDocumentsManager(): TextDocuments {
387 | if (!this.documentsManager) {
388 | this.documentsManager = new TextDocuments();
389 | }
390 |
391 | return this.documentsManager;
392 | }
393 |
394 | /**
395 | * Get the controller factory or create a PhpmdControllerFactory if no controller factory was set before
396 | *
397 | * @returns {PhpmdControllerFactory}
398 | */
399 | protected getControllerFactory(): PhpmdControllerFactory {
400 | if (!this.controllerFactory) {
401 | this.controllerFactory = new PhpmdControllerFactory();
402 | }
403 |
404 | return this.controllerFactory;
405 | }
406 |
407 | /**
408 | * Get the logger factory or create a RemoteConsoleLoggerFactory if no logger factory was set before
409 | *
410 | * @returns {ILoggerFactory}
411 | */
412 | protected getLoggerFactory(): ILoggerFactory {
413 | if (!this.loggerFactory) {
414 | this.loggerFactory = new RemoteConsoleLoggerFactory();
415 | }
416 |
417 | return this.loggerFactory;
418 | }
419 |
420 | /**
421 | * Get the notifier factory or create a ClientConnectionNotifierFactory if no notifier factory was set before
422 | *
423 | * @returns {INotifierFactory}
424 | */
425 | protected getNotifierFactory(): INotifierFactory {
426 | if (!this.notifierFactory) {
427 | this.notifierFactory = new ClientConnectionNotifierFactory();
428 | }
429 |
430 | return this.notifierFactory;
431 | }
432 |
433 | /**
434 | * Create a settings model
435 | *
436 | * Create a PHPMD settings model from the values send through the VSCode connection with some sane default values.
437 | *
438 | * @param {IPhpmdSettingsModel}
439 | */
440 | protected createSettings(values: any): IPhpmdSettingsModel {
441 | let defaults: IPhpmdSettingsModel = {
442 | enabled: true,
443 | command: "",
444 | unsafeCommandEnabled: false,
445 | unsafeCommand: "",
446 | rules: "",
447 | verbose: false,
448 | clearOnClose: true
449 | };
450 |
451 | let settings: IPhpmdSettingsModel = Object.assign(defaults, values);
452 |
453 | if (!settings.command) {
454 | settings.command = this.getDefaultCommand();
455 | }
456 |
457 | return settings;
458 | }
459 |
460 | /**
461 | * Create a environment model
462 | *
463 | * Create a PHPMD environment model from the server environment
464 | *
465 | * @param {IPhpmdEnvironmentModel}
466 | */
467 | protected createEnvironment(): IPhpmdEnvironmentModel {
468 | const environment: IPhpmdEnvironmentModel = {
469 | workspaceFolders: this.getWorkspaceFolders(),
470 | homeDir: this.getHomeDir()
471 | };
472 |
473 | return environment;
474 | }
475 |
476 | /**
477 | * Get the default command
478 | *
479 | * Get the default PHPMD command string to execute the shipped PHPMD phar file with php
480 | *
481 | * @returns {string}
482 | */
483 | protected getDefaultCommand(): string {
484 | let serverPath = Path.dirname(process.argv[1]);
485 | let phpmdPath = Path.normalize(serverPath + "/../../phpmd/phpmd.phar");
486 | let executable = "php " + phpmdPath;
487 |
488 | return executable;
489 | }
490 |
491 | /**
492 | * Create the server's PHPMD controller
493 | *
494 | * Instantiates the controller by using this server's controller factory. Arranges the controller
495 | * to use the correct connection, settings, logger and notifier. Assigns the controller to the
496 | * controller property of this server instance.
497 | *
498 | * @param {IConnection} connection
499 | * @param {IPhpmdSettingsModel} settings
500 | * @returns {void}
501 | */
502 | protected createController(connection: IConnection, settings: IPhpmdSettingsModel, environment: IPhpmdEnvironmentModel): void {
503 | let controllerFactory = this.getControllerFactory();
504 | controllerFactory.setConnection(connection);
505 | controllerFactory.setSettings(settings);
506 | controllerFactory.setEnvironment(environment);
507 |
508 | this.controller = controllerFactory.create();
509 | this.controller.setLogger(this.getLogger());
510 | this.controller.setNotifier(this.getNotifier());
511 |
512 | this.getLogger().info(`Created controller with settings '${JSON.stringify(settings)}' and environment '${JSON.stringify(environment)}'`);
513 | }
514 |
515 | /**
516 | * Create the server's logger
517 | *
518 | * Instantiates the logger by using this server's logger factory. Arranges the logger to use the
519 | * correct connection. Assigns the logger to the logger property of this server instance.
520 | *
521 | * @param {IConnection} connection
522 | * @returns {void}
523 | */
524 | protected createLogger(connection: IConnection): void {
525 | let loggerFactory = this.getLoggerFactory();
526 | loggerFactory.setConnection(connection);
527 |
528 | this.logger = loggerFactory.create();
529 | }
530 |
531 | /**
532 | * Create the server's notifier
533 | *
534 | * Instantiates the notifier by using this server's notifier factory. Arranges the notifier to use the
535 | * correct connection. Assigns the notifier to the notifier property of this server instance.
536 | *
537 | * @param {IConnection} connection
538 | * @returns {void}
539 | */
540 | protected createNotifier(connection: IConnection) {
541 | let notifierFactory = this.getNotifierFactory();
542 | notifierFactory.setConnection(connection);
543 |
544 | this.notifier = notifierFactory.create();
545 | }
546 |
547 | /**
548 | * Get the server's PHPMD controller
549 | *
550 | * Logs and throws an error if the controller was not created before calling this method.
551 | *
552 | * @throws {Error}
553 | * @returns {PhpmdController}
554 | */
555 | protected getController(): PhpmdController {
556 | if (!this.controller) {
557 | this.getLogger().error("Controller not initialized. Aborting");
558 | throw Error("Controller not initialized. Aborting.");
559 | }
560 |
561 | return this.controller;
562 | }
563 |
564 | /**
565 | * Get the server's logger
566 | *
567 | * Returns a null object implementing the ILogger interface if no logger was created before calling this method.
568 | *
569 | * @returns {ILogger}
570 | */
571 | protected getLogger(): ILogger {
572 | if (!this.logger) {
573 | return new NullLogger();
574 | }
575 |
576 | return this.logger;
577 | }
578 |
579 | /**
580 | * Get the server's notifier
581 | *
582 | * Returns a null object implementing the INotifier interface if no notifier was created before calling this method.
583 | *
584 | * @returns {INotifier}
585 | */
586 | protected getNotifier(): INotifier {
587 | if (!this.notifier) {
588 | return new NullNotifier();
589 | }
590 |
591 | return this.notifier;
592 | }
593 | }
594 |
595 | export default Server;
596 |
--------------------------------------------------------------------------------
/server/controller/PhpmdController.ts:
--------------------------------------------------------------------------------
1 | import { Pipeline } from "@open-sourcerers/j-stillery";
2 | import {
3 | CompletionItem, CompletionItemKind, createConnection, Diagnostic,
4 | DiagnosticSeverity, IConnection, InitializeParams, InitializeResult,
5 | IPCMessageReader, IPCMessageWriter, TextDocument, TextDocumentIdentifier,
6 | TextDocumentPositionParams, TextDocuments, TextDocumentSyncKind
7 | } from "vscode-languageserver";
8 | import PipelineFactory from "../factory/PipelineFactory";
9 | import PipelinePayloadFactory from "../factory/PipelinePayloadFactory";
10 | import IPhpmdSettingsModel from "../model/IPhpmdSettingsModel";
11 | import PipelineErrorModel from "../model/PipelineErrorModel";
12 | import PipelinePayloadModel from "../model/PipelinePayloadModel";
13 | import ILogger from "../service/logger/ILogger";
14 | import NullLogger from "../service/logger/NullLogger";
15 | import INotifier from "../service/notifier/INotifier";
16 | import NullNotifier from "../service/notifier/NullNotifier";
17 | import PhpmdService from "../service/PhpmdService";
18 | import PhpmdCommandBuilder from "../service/PhpmdCommandBuilder";
19 | import IPhpmdEnvironmentModel from "../model/IPhpmdEnvironmentModel";
20 |
21 | /**
22 | * PHP mess detector controller
23 | *
24 | * Defines actions available to the language server
25 | *
26 | * @module vscode-phpmd/server/controller
27 | * @author Sandhjé Bouw (sandhje@ecodes.io)
28 | */
29 | class PhpmdController {
30 | /**
31 | * Validation pipeline
32 | *
33 | * @property {Pipeline;
36 |
37 | /**
38 | * Factory class for the pipelinePayload model
39 | *
40 | * @property {PipelinePayloadFactory} pipelinePayloadFactory
41 | */
42 | private pipelinePayloadFactory: PipelinePayloadFactory;
43 |
44 | /**
45 | * Logger class
46 | *
47 | * Process log messages of various severity levels.
48 | *
49 | * @property {ILogger} logger
50 | */
51 | private logger: ILogger;
52 |
53 | /**
54 | * Notifier class
55 | *
56 | * Send end-user friendly notifications.
57 | *
58 | * @property {INotifier} notifier
59 | */
60 | private notifier: INotifier;
61 |
62 | /**
63 | * PHP mess detector command builder
64 | *
65 | * Service class used to build the phpmd command
66 | *
67 | * @property {PhpmdCommandBuilder} commandBuilder
68 | */
69 | private commandBuilder: PhpmdCommandBuilder;
70 |
71 | /**
72 | * PHP mess detector service
73 | *
74 | * Service class used to interact with the PHP mess detecter executable
75 | *
76 | * @property {PhpmdService} service
77 | */
78 | private service: PhpmdService;
79 |
80 | /**
81 | * Executable test error counter
82 | *
83 | * Keeps track of the number of times the phpmd executable was tested and the test failed.
84 | *
85 | * @property {number} phpmdTestErrorCount
86 | */
87 | private phpmdTestErrorCount: number = 0;
88 |
89 | /**
90 | * Create PHPMD controller instance with the passed connection and settings
91 | *
92 | * @param {IConnection} connection
93 | * @param {IPhpmdSettingsModel} settings
94 | */
95 | constructor(
96 | private connection: IConnection,
97 | private settings: IPhpmdSettingsModel,
98 | private environment: IPhpmdEnvironmentModel
99 | ) { }
100 |
101 | /**
102 | * Validate the passed document with PHP mess detector
103 | *
104 | * @param {TextDocument|TextDocumentIdentifier} document
105 | * @returns {Promise} Resolves with true on success, rejects with error on failure
106 | */
107 | public validate(document: TextDocument | TextDocumentIdentifier): Promise {
108 | if (!this.settings.enabled) {
109 | this.getLogger().info("Extension disabled, bailing out");
110 | return Promise.resolve(true);
111 | }
112 |
113 | this.getLogger().info("PHP Mess Detector validation started for " + document.uri, true);
114 |
115 | return new Promise((resolve, reject) => {
116 | // Test version
117 | this.getService().testPhpmd().then((data: boolean) => {
118 | let payload = this.getPipelinePayloadFactory().setUri(document.uri).create();
119 |
120 | this.getPipeline().run(payload).then((output) => {
121 | let diagnostics = output.diagnostics;
122 |
123 | // Send the computed diagnostics to VSCode.
124 | this.getLogger().info(
125 | "PHP Mess Detector validation completed for " + document.uri
126 | + ". " + diagnostics.length + " problems found",
127 | true
128 | );
129 | this.connection.sendDiagnostics({uri: output.uri, diagnostics});
130 |
131 | // Resolve the validate promise
132 | resolve(true);
133 | }, (err: any) => {
134 | // Don't notify the user of the error if silent is defined on the error and is truthy
135 | if (!err.silent) {
136 | this.getNotifier().error("An error occured while executing PHP Mess Detector");
137 | }
138 |
139 | // If an error property is defined on the error reject the validate promise with that
140 | // value instead, this is the case for PipelineErrorModel instances
141 | if (err.error) {
142 | return reject(err.error);
143 | }
144 |
145 | // Reject the validate promise
146 | return reject(err);
147 | });
148 | }, (err: Error) => {
149 | // Only notify client of "PHPMD test error" once per controller instance
150 | if (!this.phpmdTestErrorCount) {
151 | this.getNotifier().error("Unable to execute PHPMD command (" + this.settings.command + ")");
152 | }
153 |
154 | this.phpmdTestErrorCount++;
155 |
156 | // Reject the validate promise
157 | reject(err);
158 | });
159 | });
160 | }
161 |
162 | /**
163 | * Clear diagnostics for the text documents
164 | *
165 | * @param document
166 | */
167 | public clear(document: TextDocument | TextDocumentIdentifier): void {
168 | if (!this.settings.clearOnClose) {
169 | this.getLogger().info("Clearing of diagnostics disabled in settings");
170 | return;
171 | }
172 |
173 | this.getLogger().info("Clearing diagnostics for " + document.uri);
174 | this.connection.sendDiagnostics({uri: document.uri, diagnostics: []});
175 | }
176 |
177 | /**
178 | * PhpmdCommandBuilder setter
179 | *
180 | * Allows injection of phpmdCommandBuilder for better testability.
181 | *
182 | * @param {PhpmdCommandBuilder} commandBuilder
183 | * @returns {void}
184 | */
185 | public setCommandBuilder(commandBuilder: PhpmdCommandBuilder): void {
186 | this.commandBuilder = commandBuilder;
187 | }
188 |
189 | /**
190 | * PhpmdService setter
191 | *
192 | * Allows injection of phpmdService for better testability.
193 | *
194 | * @param {PhpmdService} service
195 | * @returns {void}
196 | */
197 | public setService(service: PhpmdService): void {
198 | this.service = service;
199 | }
200 |
201 | /**
202 | * Pipeline setter
203 | *
204 | * Allows injection of the validation pipeline for better testability.
205 | *
206 | * @param {Pipeline): void {
210 | this.pipeline = pipeline;
211 | }
212 |
213 | /**
214 | * PipelinePayload factory setter
215 | *
216 | * Allows injection of pipeline payload factory for better testability.
217 | *
218 | * @param {PipelinePayloadFactory} pipelinePayloadFactory
219 | * @returns {void}
220 | */
221 | public setPipelinePayloadFactory(pipelinePayloadFactory: PipelinePayloadFactory): void {
222 | this.pipelinePayloadFactory = pipelinePayloadFactory;
223 | }
224 |
225 | /**
226 | * Logger setter
227 | *
228 | * Allows injection of the logger for better testability.
229 | *
230 | * @param {ILogger} logger
231 | * @returns {void}
232 | */
233 | public setLogger(logger: ILogger): void {
234 | this.logger = logger;
235 | }
236 |
237 | /**
238 | * Notifier setter
239 | *
240 | * Allows injection of the notifier for better testability.
241 | *
242 | * @param {INotifier} notifier
243 | * @returns {void}
244 | */
245 | public setNotifier(notifier: INotifier): void {
246 | this.notifier = notifier;
247 | }
248 |
249 | /**
250 | * Get the PHP mess detector command builder
251 | *
252 | * Create a command builder based on this server's settings if no service was set before
253 | *
254 | * @returns {PhpmdCommandBuilder}
255 | */
256 | protected getCommandBuilder(): PhpmdCommandBuilder {
257 | if (!this.commandBuilder) {
258 | // TODO: add workspacefolders and homedir from settings
259 | this.commandBuilder = new PhpmdCommandBuilder(
260 | this.settings.command,
261 | this.settings.unsafeCommandEnabled,
262 | this.settings.unsafeCommand,
263 | this.environment.workspaceFolders,
264 | this.environment.homeDir
265 | );
266 | this.commandBuilder.setLogger(this.getLogger());
267 | }
268 |
269 | return this.commandBuilder;
270 | }
271 |
272 | /**
273 | * Get the PHP mess detector service
274 | *
275 | * Create a service based on this server's settings if no service was set before
276 | *
277 | * @returns {PhpmdService}
278 | */
279 | protected getService(): PhpmdService {
280 | if (!this.service) {
281 | this.service = new PhpmdService(this.getCommandBuilder());
282 | this.service.setLogger(this.getLogger());
283 | }
284 |
285 | return this.service;
286 | }
287 |
288 | /**
289 | * Get the validation pipeline
290 | *
291 | * Create a pipline based on this server's settings if no pipeline was set before
292 | *
293 | * @returns {Pipeline}
294 | */
295 | protected getPipeline(): Pipeline {
296 | if (!this.pipeline) {
297 | this.pipeline = new PipelineFactory(this.settings, this.environment, this.getLogger()).create();
298 | }
299 |
300 | return this.pipeline;
301 | }
302 |
303 | /**
304 | * Get the pipeline payload factory
305 | *
306 | * Create a pipline payload factory if no factory was set before
307 | *
308 | * @returns {PipelinePayloadFactory}
309 | */
310 | protected getPipelinePayloadFactory(): PipelinePayloadFactory {
311 | if (!this.pipelinePayloadFactory) {
312 | this.pipelinePayloadFactory = new PipelinePayloadFactory("");
313 | }
314 |
315 | return this.pipelinePayloadFactory;
316 | }
317 |
318 | /**
319 | * Get the logger
320 | *
321 | * Create a null object implementing the ILogger interface if no logger was set before
322 | *
323 | * @returns {ILogger}
324 | */
325 | protected getLogger(): ILogger {
326 | if (!this.logger) {
327 | this.logger = new NullLogger();
328 | }
329 |
330 | return this.logger;
331 | }
332 |
333 | /**
334 | * Get the notifier
335 | *
336 | * Create a null object implementing the INotifier interface if no notifier was set before
337 | *
338 | * @returns {INotifier}
339 | */
340 | protected getNotifier(): INotifier {
341 | if (!this.notifier) {
342 | this.notifier = new NullNotifier();
343 | }
344 |
345 | return this.notifier;
346 | }
347 | }
348 |
349 | export default PhpmdController;
350 |
--------------------------------------------------------------------------------
/server/factory/BuildDiagnosticsTaskFactory.ts:
--------------------------------------------------------------------------------
1 | import { Task } from "@open-sourcerers/j-stillery";
2 | import * as Xml2Js from "xml2js";
3 | import IPhpmdSettingsModel from "../model/IPhpmdSettingsModel";
4 | import PipelinePayloadModel from "../model/PipelinePayloadModel";
5 | import ILogger from "../service/logger/ILogger";
6 | import NullLogger from "../service/logger/NullLogger";
7 | import BuildDiagnosticsStrategy from "../service/pipeline/BuildDiagnosticsStrategy";
8 | import IFactory from "./IFactory";
9 |
10 | /**
11 | * BuildDiagnostics task factory
12 | *
13 | * @module vscode-phpmd/server/factory
14 | * @author Sandhjé Bouw (sandhje@ecodes.io)
15 | */
16 | class BuildDiagnosticsTaskFactory implements IFactory> {
17 | /**
18 | * @param {IPhpmdSettingsModel} settings
19 | */
20 | constructor(
21 | private settings: IPhpmdSettingsModel,
22 | private logger: ILogger = new NullLogger()
23 | ) { }
24 |
25 | /**
26 | * Create BuildDiagnostics task instance
27 | *
28 | * @see IFactory::create
29 | * @returns {Task}
30 | */
31 | public create(): Task {
32 | let strategy = new BuildDiagnosticsStrategy(this.logger);
33 |
34 | return new Task(strategy);
35 | }
36 | }
37 |
38 | export default BuildDiagnosticsTaskFactory;
39 |
--------------------------------------------------------------------------------
/server/factory/ClientConnectionNotifierFactory.ts:
--------------------------------------------------------------------------------
1 | import { IConnection } from "vscode-languageserver";
2 | import ClientConnectionNotifier from "../service/notifier/ClientConnectionNotifier";
3 | import INotifier from "../service/notifier/INotifier";
4 | import INotifierFactory from "./INotifierFactory";
5 |
6 | /**
7 | * ClientConnection notifier factory
8 | *
9 | * @module vscode-phpmd/server/factory
10 | * @author Sandhjé Bouw (sandhje@ecodes.io)
11 | */
12 | class ClientConnectionNotifierFactory implements INotifierFactory {
13 | /**
14 | * @property {IConnection} connection
15 | */
16 | private connection: IConnection;
17 |
18 | /**
19 | * Create ClientConnection notifier instance
20 | *
21 | * Throw error if no connection was set.
22 | *
23 | * @see IFactory::create
24 | * @throws {Error}
25 | * @returns {INotifier}
26 | */
27 | public create(): INotifier {
28 | if (!this.connection) {
29 | throw Error("Unable to create ClientConnectionNotifier, no connection set");
30 | }
31 |
32 | return new ClientConnectionNotifier(this.connection);
33 | }
34 |
35 | /**
36 | * Set VSCode client connection
37 | *
38 | * @see INotifierFactory::setConnection
39 | * @param {IConnection} connection
40 | * @returns {void}
41 | */
42 | public setConnection(connection: IConnection): void {
43 | this.connection = connection;
44 | }
45 | }
46 |
47 | export default ClientConnectionNotifierFactory;
48 |
--------------------------------------------------------------------------------
/server/factory/ExecuteProcessTaskFactory.ts:
--------------------------------------------------------------------------------
1 | import { Task } from "@open-sourcerers/j-stillery";
2 | import IPhpmdSettingsModel from "../model/IPhpmdSettingsModel";
3 | import PipelinePayloadModel from "../model/PipelinePayloadModel";
4 | import ILogger from "../service/logger/ILogger";
5 | import NullLogger from "../service/logger/NullLogger";
6 | import ExecuteProcessStrategy from "../service/pipeline/ExecuteProcessStrategy";
7 | import IFactory from "./IFactory";
8 | import PhpmdCommandBuilder from "../service/PhpmdCommandBuilder";
9 | import IPhpmdEnvironmentModel from "../model/IPhpmdEnvironmentModel";
10 |
11 | /**
12 | * ExecuteProcess task factory
13 | *
14 | * @module vscode-phpmd/server/factory
15 | * @author Sandhjé Bouw (sandhje@ecodes.io)
16 | */
17 | class ExecuteProcessTaskFactory implements IFactory> {
18 | /**
19 | * PHP mess detector command builder
20 | *
21 | * Service class used to build the phpmd command
22 | *
23 | * @property {PhpmdCommandBuilder} commandBuilder
24 | */
25 | private commandBuilder: PhpmdCommandBuilder;
26 |
27 | /**
28 | * @param {IPhpmdSettingsModel} settings
29 | * @param {ILogger} logger
30 | */
31 | constructor(
32 | private settings: IPhpmdSettingsModel,
33 | private environment: IPhpmdEnvironmentModel,
34 | private logger: ILogger = new NullLogger()
35 | ) { }
36 |
37 | /**
38 | * PhpmdCommandBuilder setter
39 | *
40 | * Allows injection of phpmdCommandBuilder for better testability.
41 | *
42 | * @param {PhpmdCommandBuilder} commandBuilder
43 | * @returns {void}
44 | */
45 | public setCommandBuilder(commandBuilder: PhpmdCommandBuilder): void {
46 | this.commandBuilder = commandBuilder;
47 | }
48 |
49 | /**
50 | * Get the PHP mess detector command builder
51 | *
52 | * Create a command builder based on this server's settings if no service was set before
53 | *
54 | * @returns {PhpmdCommandBuilder}
55 | */
56 | protected getCommandBuilder(): PhpmdCommandBuilder {
57 | if (!this.commandBuilder) {
58 | // TODO: add workspacefolders and homedir from settings
59 | this.commandBuilder = new PhpmdCommandBuilder(
60 | this.settings.command,
61 | this.settings.unsafeCommandEnabled,
62 | this.settings.unsafeCommand,
63 | this.environment.workspaceFolders,
64 | this.environment.homeDir
65 | );
66 | this.commandBuilder.setLogger(this.logger);
67 | }
68 |
69 | return this.commandBuilder;
70 | }
71 |
72 | /**
73 | * Create ExecuteProcess task instance
74 | *
75 | * @see IFactory::create
76 | * @returns {Task}
77 | */
78 | public create(): Task {
79 | let strategy = new ExecuteProcessStrategy(this.getCommandBuilder(), this.settings.rules, this.logger);
80 |
81 | return new Task(strategy);
82 | }
83 | }
84 |
85 | export default ExecuteProcessTaskFactory;
86 |
--------------------------------------------------------------------------------
/server/factory/IFactory.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Factory interface
3 | *
4 | * @module vscode-phpmd/server/factory
5 | * @author Sandhjé Bouw (sandhje@ecodes.io)
6 | */
7 | interface IFactory {
8 | /**
9 | * Create and return an instance of T
10 | *
11 | * @returns {T}
12 | */
13 | create: () => T;
14 | }
15 |
16 | export default IFactory;
17 |
--------------------------------------------------------------------------------
/server/factory/ILoggerFactory.ts:
--------------------------------------------------------------------------------
1 | import { IConnection } from "vscode-languageserver";
2 | import ILogger from "../service/logger/ILogger";
3 | import IFactory from "./IFactory";
4 |
5 | /**
6 | * ILogger factory interface
7 | *
8 | * @module vscode-phpmd/server/factory
9 | * @author Sandhjé Bouw (sandhje@ecodes.io)
10 | */
11 | interface ILoggerFactory extends IFactory {
12 | /**
13 | * Set logger connection
14 | *
15 | * @param {IConnection} connection
16 | * @returns {void}
17 | */
18 | setConnection(connection: IConnection): void;
19 |
20 | /**
21 | * Set logger verbose mode
22 | *
23 | * @param {boolean} verbose
24 | * @returns {void}
25 | */
26 | setVerbose(verbose: boolean): void;
27 | }
28 |
29 | export default ILoggerFactory;
30 |
--------------------------------------------------------------------------------
/server/factory/INotifierFactory.ts:
--------------------------------------------------------------------------------
1 | import { IConnection } from "vscode-languageserver";
2 | import INotifier from "../service/notifier/INotifier";
3 | import IFactory from "./IFactory";
4 |
5 | /**
6 | * INotifier factory interface
7 | *
8 | * @module vscode-phpmd/server/factory
9 | * @author Sandhjé Bouw (sandhje@ecodes.io)
10 | */
11 | interface INotifierFactory extends IFactory {
12 | /**
13 | * Set notifier connection
14 | *
15 | * @param {IConnection} connection
16 | * @returns {void}
17 | */
18 | setConnection(connection: IConnection): void;
19 | }
20 |
21 | export default INotifierFactory;
22 |
--------------------------------------------------------------------------------
/server/factory/NullLoggerFactory.ts:
--------------------------------------------------------------------------------
1 | import { IConnection } from "vscode-languageserver";
2 | import ILogger from "../service/logger/ILogger";
3 | import NullLogger from "../service/logger/NullLogger";
4 | import ILoggerFactory from "./ILoggerFactory";
5 |
6 | /**
7 | * Null logger factory
8 | *
9 | * Creates a null object implementation of the ILogger interface
10 | *
11 | * @module vscode-phpmd/server/factory
12 | * @author Sandhjé Bouw (sandhje@ecodes.io)
13 | */
14 | class NullLoggerFactory implements ILoggerFactory {
15 | /**
16 | * Create Null logger instance
17 | *
18 | * @see IFactory::create
19 | * @returns {ILogger}
20 | */
21 | public create(): ILogger {
22 | return new NullLogger();
23 | }
24 |
25 | /**
26 | * Null object implementation of set connection
27 | *
28 | * @see ILoggerFactory::setConnection
29 | * @param {IConnection} connection
30 | * @returns {void}
31 | */
32 | public setConnection(connection: IConnection): void {
33 | return;
34 | }
35 |
36 | /**
37 | * Null object implementation of set verbose
38 | *
39 | * @see ILoggerFactory::setVerbose
40 | * @param {IConnection} connection
41 | * @returns {void}
42 | */
43 | public setVerbose(verbose: boolean): void {
44 | return;
45 | }
46 | }
47 |
48 | export default NullLoggerFactory;
49 |
--------------------------------------------------------------------------------
/server/factory/NullNotifierFactory.ts:
--------------------------------------------------------------------------------
1 | import { IConnection } from "vscode-languageserver";
2 | import INotifier from "../service/notifier/INotifier";
3 | import NullNotifier from "../service/notifier/NullNotifier";
4 | import INotifierFactory from "./INotifierFactory";
5 |
6 | /**
7 | * Null notifier factory
8 | *
9 | * Creates a null object implementation of the INotifier interface
10 | *
11 | * @module vscode-phpmd/server/factory
12 | * @author Sandhjé Bouw (sandhje@ecodes.io)
13 | */
14 | class NullNotifierFactory implements INotifierFactory {
15 | /**
16 | * Create Null notifier instance
17 | *
18 | * @see IFactory::create
19 | * @returns {INotifier}
20 | */
21 | public create(): INotifier {
22 | return new NullNotifier();
23 | }
24 |
25 | /**
26 | * Null object implementation of set connection
27 | *
28 | * @see INotifierFactory::setConnection
29 | * @param {IConnection} connection
30 | * @returns {void}
31 | */
32 | public setConnection(connection: IConnection): void {
33 | return;
34 | }
35 | }
36 |
37 | export default NullNotifierFactory;
38 |
--------------------------------------------------------------------------------
/server/factory/ParseTaskFactory.ts:
--------------------------------------------------------------------------------
1 | import { Task } from "@open-sourcerers/j-stillery";
2 | import * as Xml2Js from "xml2js";
3 | import IPhpmdSettingsModel from "../model/IPhpmdSettingsModel";
4 | import PipelinePayloadModel from "../model/PipelinePayloadModel";
5 | import ParseStrategy from "../service/pipeline/ParseStrategy";
6 | import ILogger from "../service/logger/ILogger";
7 | import NullLogger from "../service/logger/NullLogger";
8 | import IFactory from "./IFactory";
9 |
10 | /**
11 | * Parse task factory
12 | *
13 | * @module vscode-phpmd/server/factory
14 | * @author Sandhjé Bouw (sandhje@ecodes.io)
15 | */
16 | class ExecuteProcessTaskFactory implements IFactory> {
17 | /**
18 | * @param {IPhpmdSettingsModels} settings
19 | * @param {ILogger} logger
20 | */
21 | constructor(
22 | private settings: IPhpmdSettingsModel,
23 | private logger: ILogger = new NullLogger()
24 | ) { }
25 |
26 | /**
27 | * Create Parse task instance
28 | *
29 | * @see IFactory::create
30 | * @returns {Task}
31 | */
32 | public create(): Task {
33 | let strategy = new ParseStrategy(this.getParser(), this.logger);
34 |
35 | return new Task(strategy);
36 | }
37 |
38 | /**
39 | * Get XML parser instance
40 | *
41 | * @returns {Xml2Js.Parser}
42 | */
43 | protected getParser(): Xml2Js.Parser {
44 | return new Xml2Js.Parser();
45 | }
46 | }
47 |
48 | export default ExecuteProcessTaskFactory;
49 |
--------------------------------------------------------------------------------
/server/factory/PhpmdControllerFactory.ts:
--------------------------------------------------------------------------------
1 | import { IConnection } from "vscode-languageserver";
2 | import PhpmdController from "../controller/PhpmdController";
3 | import IPhpmdSettingsModel from "../model/IPhpmdSettingsModel";
4 | import IPhpmdEnvironmentModel from "../model/IPhpmdEnvironmentModel";
5 | import IFactory from "./IFactory";
6 |
7 | /**
8 | * PHP mess detector controller factory
9 | *
10 | * @module vscode-phpmd/server/factory
11 | * @author Sandhjé Bouw (sandhje@ecodes.io)
12 | */
13 | class PhpmdControllerFactory implements IFactory {
14 | /**
15 | * @property {IConnection} connection
16 | */
17 | private connection: IConnection;
18 |
19 | /**
20 | * @property {IPhpmdSettingsModel} settings
21 | */
22 | private settings: IPhpmdSettingsModel;
23 |
24 | /**
25 | * @property {IPhpmdEnvironmentModel} environment
26 | */
27 | private environment: IPhpmdEnvironmentModel;
28 |
29 | /**
30 | * Create PHP mess detector controller instance
31 | *
32 | * @see IFaction::create
33 | * @returns {PhpmdController}
34 | */
35 | public create(): PhpmdController {
36 | if (!this.connection) {
37 | throw Error("Unable to create PhpmdController, no connection set");
38 | }
39 |
40 | if (!this.settings) {
41 | throw Error("Unable to create PhpmdController, no settings set");
42 | }
43 |
44 | return new PhpmdController(this.connection, this.settings, this.environment);
45 | }
46 |
47 | /**
48 | * Set VSCode client connection
49 | *
50 | * @param {IConnection} connection
51 | * @returns {void}
52 | */
53 | public setConnection(connection: IConnection) {
54 | this.connection = connection;
55 | }
56 |
57 | /**
58 | * Set vscode-phpmd settings
59 | *
60 | * @param {IPhpmdSettingsModel} settings
61 | */
62 | public setSettings(settings: IPhpmdSettingsModel) {
63 | this.settings = settings;
64 | }
65 |
66 | /**
67 | * Set vscode-phpmd environment
68 | *
69 | * @param {IPhpmdEnvironmentModel} environment
70 | */
71 | public setEnvironment(environment: IPhpmdEnvironmentModel) {
72 | this.environment = environment;
73 | }
74 | }
75 |
76 | export default PhpmdControllerFactory;
77 |
--------------------------------------------------------------------------------
/server/factory/PipelineFactory.ts:
--------------------------------------------------------------------------------
1 | import { Pipeline } from "@open-sourcerers/j-stillery";
2 | import IPhpmdSettingsModel from "../model/IPhpmdSettingsModel";
3 | import PipelinePayloadModel from "../model/PipelinePayloadModel";
4 | import ILogger from "../service/logger/ILogger";
5 | import NullLogger from "../service/logger/NullLogger";
6 | import BuildDiagnosticsTaskFactory from "./BuildDiagnosticsTaskFactory";
7 | import ExecuteProcessTaskFactory from "./ExecuteProcessTaskFactory";
8 | import IFactory from "./IFactory";
9 | import ParseTaskFactory from "./ParseTaskFactory";
10 | import TestFileTaskFactory from "./TestFileTaskFactory";
11 | import IPhpmdEnvironmentModel from "../model/IPhpmdEnvironmentModel";
12 |
13 | /**
14 | * PHP mess detector validation pipeline factory
15 | *
16 | * @module vscode-phpmd/server/factory
17 | * @author Sandhjé Bouw (sandhje@ecodes.io)
18 | */
19 | class PipelineFactory implements IFactory> {
20 | /**
21 | * @param {IPhpmdSettingsModel} settings
22 | * @param {ILogger} logger
23 | */
24 | constructor(
25 | private settings: IPhpmdSettingsModel,
26 | private environment: IPhpmdEnvironmentModel,
27 | private logger: ILogger = new NullLogger()
28 | ) { }
29 |
30 | /**
31 | * Create validation pipeline instance
32 | *
33 | * Configure pipeline with tasks:
34 | * - ExecuteProcess task
35 | * - Parse task
36 | * - BuildDiagnostics task
37 | *
38 | * @see IFaction::create
39 | * @returns {Pipeline}
40 | */
41 | public create(): Pipeline {
42 | let pipeline = new Pipeline()
43 | .pipe(new TestFileTaskFactory(this.settings, this.logger).create())
44 | .pipe(new ExecuteProcessTaskFactory(this.settings, this.environment, this.logger).create())
45 | .pipe(new ParseTaskFactory(this.settings, this.logger).create())
46 | .pipe(new BuildDiagnosticsTaskFactory(this.settings, this.logger).create());
47 |
48 | return pipeline;
49 | }
50 | }
51 |
52 | export default PipelineFactory;
53 |
--------------------------------------------------------------------------------
/server/factory/PipelinePayloadFactory.ts:
--------------------------------------------------------------------------------
1 | import { Pipeline } from "@open-sourcerers/j-stillery";
2 | import PipelinePayloadModel from "../model/PipelinePayloadModel";
3 | import IFactory from "./IFactory";
4 |
5 | /**
6 | * PHP mess detector pipeline payload model factory
7 | *
8 | * @module vscode-phpmd/server/factory
9 | * @author Sandhjé Bouw (sandhje@ecodes.io)
10 | */
11 | class PipelinePayloadFactory implements IFactory {
12 | /**
13 | * @param {string} uri
14 | */
15 | constructor(
16 | private uri: string
17 | ) { }
18 |
19 | /**
20 | * Set file URI to be used upon creating a new instance of the model
21 | *
22 | * @param uri
23 | */
24 | public setUri(uri: string) {
25 | this.uri = uri;
26 |
27 | return this;
28 | }
29 |
30 | /**
31 | * Create pipeline payload model instance
32 | *
33 | * @see IFaction::create
34 | * @returns {PipelinePayloadModel}
35 | */
36 | public create(): PipelinePayloadModel {
37 | return new PipelinePayloadModel(this.uri);
38 | }
39 | }
40 |
41 | export default PipelinePayloadFactory;
42 |
--------------------------------------------------------------------------------
/server/factory/RemoteConsoleLoggerFactory.ts:
--------------------------------------------------------------------------------
1 | import { IConnection } from "vscode-languageserver";
2 | import ILogger from "../service/logger/ILogger";
3 | import RemoteConsoleLogger from "../service/logger/RemoteConsoleLogger";
4 | import ILoggerFactory from "./ILoggerFactory";
5 |
6 | /**
7 | * Remote console logger factory
8 | *
9 | * @module vscode-phpmd/server/factory
10 | * @author Sandhjé Bouw (sandhje@ecodes.io)
11 | */
12 | class RemoteConsoleLoggerFactory implements ILoggerFactory {
13 | /**
14 | * @property {IConnection} connection
15 | */
16 | private connection: IConnection;
17 |
18 | /**
19 | * @property {boolean} verbose
20 | */
21 | private verbose: boolean = false;
22 |
23 | /**
24 | * Create remote console logger instance
25 | *
26 | * @see IFaction::create
27 | * @returns {ILogger}
28 | */
29 | public create(): ILogger {
30 | if (!this.connection) {
31 | throw Error("Unable to create RemoteConsoleLogger, no connection set");
32 | }
33 |
34 | return new RemoteConsoleLogger(this.connection.console, this.verbose);
35 | }
36 |
37 | /**
38 | * Set VSCode client connection
39 | *
40 | * @see ILoggerFactory::setConnection
41 | * @param {IConnection} connection
42 | * @returns {void}
43 | */
44 | public setConnection(connection: IConnection) {
45 | this.connection = connection;
46 | }
47 |
48 | /**
49 | * Set verbose mode
50 | *
51 | * @see ILoggerFactory::setVerbose
52 | * @param {boolean} verbose
53 | * @returns {void}
54 | */
55 | public setVerbose(verbose: boolean) {
56 | this.verbose = verbose;
57 | }
58 | }
59 |
60 | export default RemoteConsoleLoggerFactory;
61 |
--------------------------------------------------------------------------------
/server/factory/TestFileTaskFactory.ts:
--------------------------------------------------------------------------------
1 | import { Task } from "@open-sourcerers/j-stillery";
2 | import * as fs from "fs";
3 | import IPhpmdSettingsModel from "../model/IPhpmdSettingsModel";
4 | import PipelinePayloadModel from "../model/PipelinePayloadModel";
5 | import ILogger from "../service/logger/ILogger";
6 | import NullLogger from "../service/logger/NullLogger";
7 | import TestFileStrategy from "../service/pipeline/TestFileStrategy";
8 | import IFactory from "./IFactory";
9 |
10 | /**
11 | * Test file task factory
12 | *
13 | * @module vscode-phpmd/server/factory
14 | * @author Sandhjé Bouw (sandhje@ecodes.io)
15 | */
16 | class TestFileTaskFactory implements IFactory> {
17 | /**
18 | * Test file factory constructor
19 | *
20 | * @param {IPhpmdSettingsModel} settings
21 | * @param {ILogger} logger
22 | */
23 | constructor(
24 | private settings: IPhpmdSettingsModel,
25 | private logger: ILogger = new NullLogger()
26 | ) { }
27 |
28 | /**
29 | * Create TestFile pipeline task instance
30 | *
31 | * @see IFactory::create
32 | * @returns {Task}
33 | */
34 | public create(): Task {
35 | let strategy = new TestFileStrategy(fs.readFile, this.logger);
36 |
37 | return new Task(strategy);
38 | }
39 | }
40 |
41 | export default TestFileTaskFactory;
42 |
--------------------------------------------------------------------------------
/server/init.ts:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | import Server from "./Server";
4 |
5 | // Initialise the server and run
6 | (new Server()).main();
7 |
--------------------------------------------------------------------------------
/server/model/IPhpmdEnvironmentModel.ts:
--------------------------------------------------------------------------------
1 | import { WorkspaceFolder } from "vscode-languageserver";
2 |
3 | /**
4 | * VSCode PHPMD environment model
5 | *
6 | * @module vscode-phpmd/server/model
7 | * @author Sandhjé Bouw (sandhje@ecodes.io)
8 | */
9 | interface IPhpmdEnvironmentModel {
10 | /**
11 | * WorkspaceFolders
12 | *
13 | * @property {WorkspaceFolder[]}
14 | */
15 | workspaceFolders: WorkspaceFolder[];
16 |
17 | /**
18 | * OS Home dir
19 | *
20 | * @property {string}
21 | */
22 | homeDir: string;
23 | }
24 |
25 | export default IPhpmdEnvironmentModel;
26 |
--------------------------------------------------------------------------------
/server/model/IPhpmdSettingsModel.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * VSCode PHPMD settings model
3 | *
4 | * @module vscode-phpmd/server/model
5 | * @author Sandhjé Bouw (sandhje@ecodes.io)
6 | */
7 | interface IPhpmdSettingsModel {
8 | /**
9 | * Enabled
10 | *
11 | * @property {boolean}
12 | */
13 | enabled: boolean;
14 |
15 | /**
16 | * PHP mess detector command
17 | *
18 | * @property {string}
19 | */
20 | command: string;
21 |
22 | /**
23 | * PHP mess detector unsafe command enabled
24 | *
25 | * @property {boolean}
26 | */
27 | unsafeCommandEnabled: boolean;
28 |
29 | /**
30 | * PHP mess detector unsafe command
31 | *
32 | * @property {string}
33 | */
34 | unsafeCommand: string;
35 |
36 | /**
37 | * PHP mess detector rules
38 | *
39 | * @property {string}
40 | */
41 | rules: string;
42 |
43 | /**
44 | * Verbose mode
45 | *
46 | * @property {boolean}
47 | */
48 | verbose: boolean;
49 |
50 | /**
51 | * Clear errors on close
52 | *
53 | * @property {boolean}
54 | */
55 | clearOnClose: boolean;
56 | }
57 |
58 | export default IPhpmdSettingsModel;
59 |
--------------------------------------------------------------------------------
/server/model/PipelineErrorModel.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * PHP mess detector validation pipeline error interface
3 | *
4 | * @module vscode-phpmd/server/model
5 | * @author Sandhjé Bouw (sandhje@ecodes.io)
6 | */
7 | class PipelineErrorModel {
8 | /**
9 | * Pipeline error model constructor
10 | *
11 | * @param {any} error Original error object
12 | * @param {boolean} silent Flag indicating wether the client should show a notification to the user or not
13 | */
14 | public constructor(
15 | public error: any,
16 | public silent: boolean = false
17 | ) { }
18 | }
19 |
20 | export default PipelineErrorModel;
21 |
--------------------------------------------------------------------------------
/server/model/PipelinePayloadModel.ts:
--------------------------------------------------------------------------------
1 | import { Diagnostic } from "vscode-languageserver";
2 | import { URI } from "vscode-uri";
3 | import { IPmd } from "./pmd";
4 |
5 | /**
6 | * PHP mess detector validation pipeline payload interface
7 | *
8 | * @module vscode-phpmd/server/model
9 | * @author Sandhjé Bouw (sandhje@ecodes.io)
10 | */
11 | class PipelinePayloadModel {
12 | /**
13 | * URI of file to be validated
14 | *
15 | * @property {string}
16 | */
17 | public uri: string = "";
18 |
19 | /**
20 | * File path for file to be validated
21 | *
22 | * @readonly
23 | * @property {string}
24 | */
25 | public get path(): string {
26 | return URI.parse(this.uri).fsPath;
27 | }
28 |
29 | /**
30 | * Raw validation result
31 | *
32 | * @property {string}
33 | */
34 | public raw: string = "";
35 |
36 | /**
37 | * Parsed validation result
38 | *
39 | * @property {IPmd}
40 | */
41 | public pmd: IPmd;
42 |
43 | /**
44 | * List of VSCode diagnosticts
45 | *
46 | * @property {Diagnostic[]}
47 | */
48 | public diagnostics: Diagnostic[] = [];
49 |
50 | /**
51 | * Validation pipeline payload model constructor
52 | *
53 | * @param {string} uri URI of file to be validated
54 | */
55 | public constructor(uri: string) {
56 | this.uri = uri;
57 | }
58 | }
59 |
60 | export default PipelinePayloadModel;
61 |
--------------------------------------------------------------------------------
/server/model/pmd/IPmd.ts:
--------------------------------------------------------------------------------
1 | import { IPmdFileData } from "./IPmdFileData";
2 | import { IPmdMetaData } from "./IPmdMetaData";
3 |
4 | /**
5 | * PHP mess detector output interface
6 | *
7 | * @module vscode-phpmd/server/model/pmd
8 | * @author Sandhjé Bouw (sandhje@ecodes.io)
9 | */
10 | export interface IPmd {
11 | /**
12 | * PHP mess detector metadata
13 | *
14 | * @property {IPmdMetaData}
15 | */
16 | $: IPmdMetaData;
17 |
18 | /**
19 | * PHP mess detector file data
20 | *
21 | * @property {IPmdFileData}
22 | */
23 | file: IPmdFileData[];
24 | }
25 |
--------------------------------------------------------------------------------
/server/model/pmd/IPmdFileData.ts:
--------------------------------------------------------------------------------
1 | import { IPmdFileMetaData } from "./IPmdFileMetaData";
2 | import { IPmdViolation } from "./IPmdViolation";
3 |
4 | /**
5 | * PHP mess detector file data interface
6 | *
7 | * @module vscode-phpmd/server/model/pmd
8 | * @author Sandhjé Bouw (sandhje@ecodes.io)
9 | */
10 | export interface IPmdFileData {
11 | /**
12 | * PHP mess detector file metadata
13 | *
14 | * @property {IPmdFileMetaData}
15 | */
16 | $: IPmdFileMetaData;
17 |
18 | /**
19 | * PHP mess detector violations list
20 | *
21 | * @property {IPmdViolation[]}
22 | */
23 | violation: IPmdViolation[];
24 | }
25 |
--------------------------------------------------------------------------------
/server/model/pmd/IPmdFileMetaData.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * PHP mess detector file metadata interface
3 | *
4 | * @module vscode-phpmd/server/model/pmd
5 | * @author Sandhjé Bouw (sandhje@ecodes.io)
6 | */
7 | export interface IPmdFileMetaData {
8 | /**
9 | * Filename
10 | *
11 | * @property {string}
12 | */
13 | name: string;
14 | }
15 |
--------------------------------------------------------------------------------
/server/model/pmd/IPmdMetaData.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * PHP mess detector metadata interface
3 | *
4 | * @module vscode-phpmd/server/model/pmd
5 | * @author Sandhjé Bouw (sandhje@ecodes.io)
6 | */
7 | export interface IPmdMetaData {
8 | /**
9 | * Report timestamp
10 | *
11 | * @property {string}
12 | */
13 | timestamp: string;
14 |
15 | /**
16 | * PHP mess detector version
17 | *
18 | * @property {string}
19 | */
20 | version: string;
21 | }
22 |
--------------------------------------------------------------------------------
/server/model/pmd/IPmdViolation.ts:
--------------------------------------------------------------------------------
1 | import { IPmdViolationMetaData } from "./IPmdViolationMetaData";
2 |
3 | /**
4 | * PHP mess detector violation interface
5 | *
6 | * @module vscode-phpmd/server/model/pmd
7 | * @author Sandhjé Bouw (sandhje@ecodes.io)
8 | */
9 | export interface IPmdViolation {
10 | /**
11 | * PHP mess detector violation message
12 | *
13 | * @property {string}
14 | */
15 | _: string;
16 |
17 | /**
18 | * PHP mess detector violation metadata
19 | *
20 | * @property {IPmdViolationMetaData}
21 | */
22 | $: IPmdViolationMetaData;
23 | }
24 |
--------------------------------------------------------------------------------
/server/model/pmd/IPmdViolationMetaData.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * PHP mess detector violation metadata interface
3 | *
4 | * @module vscode-phpmd/server/model/pmd
5 | * @author Sandhjé Bouw (sandhje@ecodes.io)
6 | */
7 | export interface IPmdViolationMetaData {
8 | /**
9 | * PHP mess detector violation begin line
10 | *
11 | * @property {string}
12 | */
13 | beginline: string;
14 |
15 | /**
16 | * PHP mess detector violation class name
17 | *
18 | * @property {string}
19 | */
20 | class: string;
21 |
22 | /**
23 | * PHP mess detector violation end line
24 | *
25 | * @property {string}
26 | */
27 | endline: string;
28 |
29 | /**
30 | * PHP mess detector violation external info url
31 | *
32 | * @property {string}
33 | */
34 | externalInfoUrl?: string;
35 |
36 | /**
37 | * PHP mess detector violation package
38 | *
39 | * @property {string}
40 | */
41 | package: string;
42 |
43 | /**
44 | * PHP mess detector violation priority
45 | *
46 | * @property {string}
47 | */
48 | priority: string;
49 |
50 | /**
51 | * PHP mess detector violation rule
52 | *
53 | * @property {string}
54 | */
55 | rule: string;
56 |
57 | /**
58 | * PHP mess detector violation ruleset
59 | *
60 | * @property {string}
61 | */
62 | ruleset: string;
63 | }
64 |
--------------------------------------------------------------------------------
/server/model/pmd/index.ts:
--------------------------------------------------------------------------------
1 | export { IPmd } from "./IPmd";
2 | export { IPmdFileData } from "./IPmdFileData";
3 | export { IPmdFileMetaData } from "./IPmdFileMetaData";
4 | export { IPmdMetaData } from "./IPmdMetaData";
5 | export { IPmdViolation } from "./IPmdViolation";
6 | export { IPmdViolationMetaData } from "./IPmdViolationMetaData";
7 |
--------------------------------------------------------------------------------
/server/service/PhpmdCommandBuilder.ts:
--------------------------------------------------------------------------------
1 | import ILogger from "./logger/ILogger";
2 | import NullLogger from "./logger/NullLogger";
3 | import { URI } from "vscode-uri";
4 | import { WorkspaceFolder } from "vscode-languageserver";
5 |
6 | /**
7 | * PHP mess detector command builder
8 | *
9 | * Builder for the php mess detector command
10 | *
11 | * @module vscode-phpmd/service
12 | * @author Sandhjé Bouw (sandhje@ecodes.io)
13 | */
14 | class PhpmdCommandBuilder {
15 | /**
16 | * Logger
17 | *
18 | * @property {ILogger}
19 | */
20 | private logger: ILogger;
21 |
22 | /**
23 | * Command
24 | *
25 | * @property {string}
26 | */
27 | private readonly command: string;
28 |
29 | /**
30 | * Service constructor
31 | *
32 | * Takes the command string and variable for substitution
33 | *
34 | * @param {string} command
35 | * @param {WorkspaceFolder[]} workspaceFolders
36 | * @param {string} homeDir
37 | */
38 | constructor(command: string, unsafeCommandEnabled: boolean, unsafeCommand: string, private workspaceFolders: WorkspaceFolder[], private homeDir: string) {
39 | let cmd = command;
40 |
41 | if (unsafeCommandEnabled && unsafeCommand)
42 | cmd = unsafeCommand;
43 |
44 | this.command = cmd;
45 | }
46 |
47 | public usingGlobalPhp(): boolean {
48 | return this.command.toLowerCase().substr(0, 4) === "php ";
49 | }
50 |
51 | /**
52 | * Build the PHP mess detector version command.
53 | *
54 | * @returns {string}
55 | */
56 | public buildPhpmdVersionCommand(): string {
57 | const command = `${this.command} --version`;
58 | this.getLogger().info(`Building phpmd version command: ${command}`, true);
59 |
60 | return command;
61 | }
62 |
63 | /**
64 | * Build the PHP mess detector command.
65 | *
66 | * @param {string} options A string with PHP mess detector command line options
67 | * @returns {string}
68 | */
69 | public buildPhpmdCommand(options: string): string {
70 | // Replace homedir in options
71 | if (options.indexOf("\"~/") > -1)
72 | options = options.replace(`"~/`, `"${this.homeDir}/`);
73 |
74 | if (options.indexOf("\"~\\") > -1)
75 | options = options.replace(`"~\\`, `"${this.homeDir}\\`);
76 |
77 | // Replace workspaceFolder in options
78 | if (options.indexOf("${workspaceFolder}") > -1)
79 | {
80 | const file = this.getFileFromOptions(options);
81 | const folder = this.getWorkspaceFolderForFile(file);
82 |
83 | if (file && folder) {
84 | options = options.replace("${workspaceFolder}", folder);
85 | }
86 | }
87 |
88 | const command = `${this.command} ${options}`;
89 | this.getLogger().info(`Building phpmd command: ${command}`, true);
90 |
91 | return command;
92 | }
93 |
94 | /**
95 | * Logger setter
96 | *
97 | * @param {ILogger} logger
98 | * @returns {void}
99 | */
100 | public setLogger(logger: ILogger): void {
101 | this.logger = logger;
102 | }
103 |
104 | /**
105 | * Logger getter
106 | *
107 | * Returns null object logger if the setter was not called before
108 | *
109 | * @returns {ILogger}
110 | */
111 | protected getLogger(): ILogger {
112 | if (!this.logger) {
113 | this.logger = new NullLogger();
114 | }
115 |
116 | return this.logger;
117 | }
118 |
119 | protected getFileFromOptions(options: string): string {
120 | if (!options)
121 | return undefined;
122 |
123 | const optionsSegments = options.split(" ");
124 | const fileSegment = optionsSegments[0];
125 |
126 | if (!fileSegment || fileSegment.length <= 2)
127 | return undefined;
128 |
129 | return fileSegment.substring(1, fileSegment.length - 1);
130 | }
131 |
132 | protected getWorkspaceFolderForFile(file: string): string {
133 | if (!file)
134 | return undefined;
135 |
136 | if (!this.workspaceFolders || !this.workspaceFolders.length)
137 | return undefined;
138 |
139 | let workspaceFolder = this.workspaceFolders.find(folder => {
140 | const folderPath = URI.parse(folder.uri).fsPath;
141 | const result = file.startsWith(folderPath);
142 |
143 | if (result)
144 | this.getLogger().info(`Found match between file and workspace folder (file: "${file}", workspaceFolder: "${folderPath}")`, true);
145 |
146 | return result;
147 | });
148 |
149 | if (!workspaceFolder) {
150 | this.getLogger().info(`No matching workspace folder found for file "${file}"`, true);
151 | this.getLogger().info(`Using first folder in workspace folder array: "${URI.parse(this.workspaceFolders[0].uri).fsPath}"`, true);
152 | workspaceFolder = this.workspaceFolders[0];
153 | }
154 |
155 | return URI.parse(workspaceFolder.uri).fsPath;
156 | }
157 | }
158 |
159 | export default PhpmdCommandBuilder;
--------------------------------------------------------------------------------
/server/service/PhpmdService.ts:
--------------------------------------------------------------------------------
1 | import * as Process from "child_process";
2 | import ILogger from "./logger/ILogger";
3 | import NullLogger from "./logger/NullLogger";
4 | import PhpmdCommandBuilder from "./PhpmdCommandBuilder";
5 |
6 | /**
7 | * PHP mess detector service
8 | *
9 | * Interaction layer with the PHP mess detector command.
10 | *
11 | * @module vscode-phpmd/service
12 | * @author Sandhjé Bouw (sandhje@ecodes.io)
13 | */
14 | class PhpmdService {
15 | /**
16 | * NodeJS process executor
17 | *
18 | * @property {(command: sstring) => Process.ChildProcess}
19 | */
20 | private exec: (command: string) => Process.ChildProcess;
21 |
22 | /**
23 | * Logger
24 | *
25 | * @property {ILogger}
26 | */
27 | private logger: ILogger;
28 |
29 | /**
30 | * Service constructor
31 | *
32 | * Takes the PHP mess detector command to be worked with
33 | *
34 | * @param {PhpmdCommandBuilder} commandBuilder
35 | */
36 | constructor(private commandBuilder: PhpmdCommandBuilder) { }
37 |
38 | /**
39 | * PHP availability test
40 | *
41 | * Tests wether the PHP command is globally available on the system. Resolves with a boolean true
42 | * if available or if the PHP mess detector command does not use the PHP command. Rejects with an
43 | * error if neither of these conditions apply.
44 | *
45 | * @returns {Promise}
46 | */
47 | public testPhp(): Promise {
48 | if (!this.commandBuilder.usingGlobalPhp()) {
49 | this.getLogger().info("PHP Mess Detector command not using global PHP, skipping PHP test", true);
50 | return Promise.resolve(true);
51 | }
52 |
53 | return new Promise((resolve, reject) => {
54 | this.execute("php -v").then((data) => {
55 | this.getLogger().info("PHP command test successful (" + data.substr(0, 16).trim() + " ...)", true);
56 | resolve(true);
57 | }, (err: Error) => {
58 | reject(err);
59 | });
60 | });
61 | }
62 |
63 | /**
64 | * PHP mess detector availability test
65 | *
66 | * Tests wether the PHP mess detector command can be executed. Resolves with a boolean true if
67 | * the command was successfully exectuted. Rejects with an error if not.
68 | *
69 | * @returns {Promise}
70 | */
71 | public testPhpmd(): Promise {
72 | return new Promise((resolve, reject) => {
73 | this.testPhp().then(() => {
74 | return this.execute(this.commandBuilder.buildPhpmdVersionCommand());
75 | }).then((data) => {
76 | this.getLogger().info("PHP Mess Detector test successful (" + data.trim() + ")", true);
77 | resolve(true);
78 | }, (err: Error) => {
79 | reject(err);
80 | });
81 | });
82 | }
83 |
84 | /**
85 | * Run the PHP mess detector command.
86 | *
87 | * Note: checking for availability of the command is the consumers responsibility and done through
88 | * the testPhpmd method.
89 | *
90 | * @param {string} options A string with PHP mess detector command line options
91 | * @returns {Promise}
92 | */
93 | public run(options: string): Promise {
94 | this.getLogger().info(`Running phpmd command with options "${options}"`, true);
95 | return this.execute(this.commandBuilder.buildPhpmdCommand(options));
96 | }
97 |
98 | /**
99 | * Logger setter
100 | *
101 | * @param {ILogger} logger
102 | * @returns {void}
103 | */
104 | public setLogger(logger: ILogger): void {
105 | this.logger = logger;
106 | }
107 |
108 | /**
109 | * Executor setter
110 | *
111 | * Allows overriding the executor for testing purposes
112 | *
113 | * @param {(command: string) => Process.ChildProcess} exec
114 | * @returns {void}
115 | */
116 | public setExecutor(exec: (command: string) => Process.ChildProcess) {
117 | this.exec = exec;
118 | }
119 |
120 | /**
121 | * Executor getter
122 | *
123 | * Returns NodeJS process executor if the setter was not called before.
124 | *
125 | * @returns {(command: string) => Process.ChildProcess}
126 | */
127 | protected getExecutor(): (command: string) => Process.ChildProcess {
128 | if (!this.exec) {
129 | this.exec = Process.exec;
130 | }
131 |
132 | return this.exec;
133 | }
134 |
135 | /**
136 | * Logger getter
137 | *
138 | * Returns null object logger if the setter was not called before
139 | *
140 | * @returns {ILogger}
141 | */
142 | protected getLogger(): ILogger {
143 | if (!this.logger) {
144 | this.logger = new NullLogger();
145 | }
146 |
147 | return this.logger;
148 | }
149 |
150 | /**
151 | * Execute the passed command with this instance's executor
152 | *
153 | * @param {string} cmd
154 | * @returns {Promise}
155 | */
156 | protected execute(cmd): Promise {
157 | return new Promise((resolve, reject) => {
158 | let result: string;
159 | let exec = this.getExecutor();
160 | let process: Process.ChildProcess = exec(cmd);
161 |
162 | process.stdout.setEncoding("utf8");
163 |
164 | process.stdout.on("data", (data) => {
165 | if (result) {
166 | data = result + data.toString();
167 | }
168 |
169 | result = data.toString();
170 | });
171 |
172 | process.stdout.on("close", () => {
173 | if (!result) {
174 | reject(Error("An error occured, no output was received after executing the phpmd command"));
175 | return;
176 | }
177 |
178 | resolve(result);
179 | });
180 |
181 | process.stdout.on("error", (err: Error) => {
182 | reject(err);
183 | });
184 | });
185 | }
186 | }
187 |
188 | export default PhpmdService;
189 |
--------------------------------------------------------------------------------
/server/service/logger/ILogger.ts:
--------------------------------------------------------------------------------
1 | import { IConnection, ClientCapabilities, ServerCapabilities } from "vscode-languageserver";
2 |
3 | /**
4 | * Logger interface
5 | *
6 | * @module vscode-phpmd/service/logger
7 | * @author Sandhjé Bouw (sandhje@ecodes.io)
8 | */
9 | interface ILogger {
10 | /**
11 | * Set the verbose property flag
12 | *
13 | * @param {boolean} verbose The verbose flag
14 | * @returns {this}
15 | */
16 | setVerbose(verbose: boolean): this;
17 |
18 | /**
19 | * Get the verbose property flag
20 | *
21 | * @returns {boolean}
22 | * @returns {this}
23 | */
24 | getVerbose(): boolean;
25 |
26 | /**
27 | * Show an error message
28 | *
29 | * @param {string} message The message to show
30 | * @param {boolean} isVerbose If set to true only shows the message if the logger's verbose property is truthy
31 | * @returns {this}
32 | */
33 | error(message: string, isVerbose?: boolean): this;
34 |
35 | /**
36 | * Show a warning message
37 | *
38 | * @param {string} message The message to show
39 | * @param {boolean} isVerbose If set to true only shows the message if the logger's verbose property is truthy
40 | * @returns {this}
41 | */
42 | warn(message: string, isVerbose?: boolean): this;
43 |
44 | /**
45 | * Show an information message
46 | *
47 | * @param {string} message The message to show
48 | * @param {boolean} isVerbose If set to true only shows the message if the logger's verbose property is truthy
49 | * @returns {this}
50 | */
51 | info(message: string, isVerbose?: boolean): this;
52 |
53 | /**
54 | * Log a message
55 | *
56 | * @param {string} message The message to log
57 | * @param {boolean} isVerbose If set to true only shows the message if the logger's verbose property is truthy
58 | * @returns {this}
59 | */
60 | log(message: string, isVerbose?: boolean): this;
61 |
62 | /**
63 | * Attach the remote to the given connection.
64 | *
65 | * @param connection The connection this remote is operating on.
66 | */
67 | attach(connection: IConnection): void;
68 | /**
69 | * The connection this remote is attached to.
70 | */
71 | connection: IConnection;
72 | /**
73 | * Called to initialize the remote with the given
74 | * client capabilities
75 | *
76 | * @param capabilities The client capabilities
77 | */
78 | initialize(capabilities: ClientCapabilities): void;
79 | /**
80 | * Called to fill in the server capabilities this feature implements.
81 | *
82 | * @param capabilities The server capabilities to fill.
83 | */
84 | fillServerCapabilities(capabilities: ServerCapabilities): void;
85 | }
86 |
87 | export default ILogger;
88 |
--------------------------------------------------------------------------------
/server/service/logger/NullLogger.ts:
--------------------------------------------------------------------------------
1 | import ILogger from "./ILogger";
2 | import { IConnection, ClientCapabilities, ServerCapabilities } from "vscode-languageserver";
3 |
4 | /**
5 | * Null object implementation of ILogger
6 | *
7 | * @module vscode-phpmd/service/logger
8 | * @author Sandhjé Bouw (sandhje@ecodes.io)
9 | */
10 | class NullLogger implements ILogger {
11 | attach(connection: IConnection): void {
12 | throw new Error("Method not implemented.");
13 | }
14 | connection: IConnection;
15 | initialize(capabilities: ClientCapabilities): void {
16 | throw new Error("Method not implemented.");
17 | }
18 | fillServerCapabilities(capabilities: ServerCapabilities): void {
19 | throw new Error("Method not implemented.");
20 | }
21 | /**
22 | * Verbose flag
23 | *
24 | * @property {boolean}
25 | */
26 | private verbose: boolean = false;
27 |
28 | /**
29 | * @see ILogger::setVerbose
30 | */
31 | public setVerbose(verbose: boolean): this {
32 | this.verbose = verbose;
33 |
34 | return this;
35 | }
36 |
37 | /**
38 | * @see ILogger::getVerbose
39 | */
40 | public getVerbose(): boolean {
41 | return this.verbose;
42 | }
43 |
44 | /**
45 | * @see ILogger::error
46 | */
47 | public error(message: string, isVerbose?: boolean): this {
48 | return this;
49 | }
50 |
51 | /**
52 | * @see ILogger::warn
53 | */
54 | public warn(message: string, isVerbose?: boolean): this {
55 | return this;
56 | }
57 |
58 | /**
59 | * @see ILogger::info
60 | */
61 | public info(message: string, isVerbose?: boolean): this {
62 | return this;
63 | }
64 |
65 | /**
66 | * @see ILogger::log
67 | */
68 | public log(message: string, isVerbose?: boolean): this {
69 | return this;
70 | }
71 | }
72 |
73 | export default NullLogger;
74 |
--------------------------------------------------------------------------------
/server/service/logger/RemoteConsoleLogger.ts:
--------------------------------------------------------------------------------
1 | import { RemoteConsole, IConnection, ClientCapabilities, ServerCapabilities } from "vscode-languageserver";
2 | import ILogger from "./ILogger";
3 |
4 | /**
5 | * Remote console implementation of ILogger
6 | *
7 | * @module vscode-phpmd/service/logger
8 | * @author Sandhjé Bouw (sandhje@ecodes.io)
9 | */
10 | class RemoteConsoleLogger implements ILogger {
11 | /**
12 | * Remote console logger constructor
13 | *
14 | * @param {RemoteConsole} remoteConsole
15 | * @param {boolean} verbose
16 | */
17 | constructor(private remoteConsole: RemoteConsole, private verbose: boolean = false) {}
18 |
19 | /**
20 | * Attach the remote to the given connection.
21 | *
22 | * @param connection The connection this remote is operating on.
23 | */
24 | attach(connection: IConnection): void {
25 | throw new Error("Method not implemented.");
26 | };
27 |
28 | /**
29 | * The connection this remote is attached to.
30 | */
31 | connection: IConnection;
32 |
33 | /**
34 | * Called to initialize the remote with the given
35 | * client capabilities
36 | *
37 | * @param capabilities The client capabilities
38 | */
39 | initialize(capabilities: ClientCapabilities): void {
40 | throw new Error("Method not implemented.");
41 | }
42 |
43 | /**
44 | * Called to fill in the server capabilities this feature implements.
45 | *
46 | * @param capabilities The server capabilities to fill.
47 | */
48 | fillServerCapabilities(capabilities: ServerCapabilities): void {
49 | throw new Error("Method not implemented.");
50 | }
51 |
52 | /**
53 | * @see ILogger::setVerbose
54 | */
55 | public setVerbose(verbose: boolean): this {
56 | this.verbose = verbose;
57 |
58 | return this;
59 | }
60 |
61 | /**
62 | * @see ILogger::getVerbose
63 | */
64 | public getVerbose(): boolean {
65 | return this.verbose;
66 | }
67 |
68 | /**
69 | * @see ILogger::error
70 | */
71 | public error(message: string, isVerbose?: boolean): this {
72 | if (!isVerbose || this.getVerbose() === true) {
73 | this.remoteConsole.error(message);
74 | }
75 |
76 | return this;
77 | }
78 |
79 | /**
80 | * @see ILogger::warn
81 | */
82 | public warn(message: string, isVerbose?: boolean): this {
83 | if (!isVerbose || this.getVerbose() === true) {
84 | this.remoteConsole.warn(message);
85 | }
86 |
87 | return this;
88 | }
89 |
90 | /**
91 | * @see ILogger::info
92 | */
93 | public info(message: string, isVerbose?: boolean): this {
94 | if (!isVerbose || this.getVerbose() === true) {
95 | this.remoteConsole.info(message);
96 | }
97 |
98 | return this;
99 | }
100 |
101 | /**
102 | * @see ILogger::log
103 | */
104 | public log(message: string, isVerbose?: boolean): this {
105 | if (!isVerbose || this.getVerbose() === true) {
106 | this.remoteConsole.log(message);
107 | }
108 |
109 | return this;
110 | }
111 | }
112 |
113 | export default RemoteConsoleLogger;
114 |
--------------------------------------------------------------------------------
/server/service/notifier/ClientConnectionNotifier.ts:
--------------------------------------------------------------------------------
1 | import { IConnection, MessageType, ShowMessageNotification } from "vscode-languageserver";
2 | import INotifier from "./INotifier";
3 |
4 | /**
5 | * Client connection implementation of INotifier
6 | *
7 | * @module vscode-phpmd/service/notifier
8 | * @author Sandhjé Bouw (sandhje@ecodes.io)
9 | */
10 | class ClientConnectionNotifier implements INotifier {
11 | /**
12 | * Client connection notifier constructor
13 | *
14 | * @param {IConnection} connection
15 | */
16 | constructor(private connection: IConnection) {}
17 |
18 | /**
19 | * @see INotifier::error
20 | */
21 | public error(message: string) {
22 | this.connection.sendNotification(ShowMessageNotification.type, {
23 | message,
24 | type: MessageType.Error,
25 | });
26 |
27 | return this;
28 | }
29 | }
30 |
31 | export default ClientConnectionNotifier;
32 |
--------------------------------------------------------------------------------
/server/service/notifier/INotifier.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Notifier interface
3 | *
4 | * @module vscode-phpmd/service/notifier
5 | * @author Sandhjé Bouw (sandhje@ecodes.io)
6 | */
7 | interface INotifier {
8 | /**
9 | * Show an error notification to the user
10 | *
11 | * @param {string} message The message to show
12 | * @returns {this}
13 | */
14 | error(message: string): this;
15 | }
16 |
17 | export default INotifier;
18 |
--------------------------------------------------------------------------------
/server/service/notifier/NullNotifier.ts:
--------------------------------------------------------------------------------
1 | import INotifier from "./INotifier";
2 |
3 | /**
4 | * Null object implementation of INotifier
5 | *
6 | * @module vscode-phpmd/service/notifier
7 | * @author Sandhjé Bouw (sandhje@ecodes.io)
8 | */
9 | class NullNotifier implements INotifier {
10 | /**
11 | * @see INotifier::error
12 | */
13 | public error(message: string): this {
14 | return this;
15 | }
16 | }
17 |
18 | export default NullNotifier;
19 |
--------------------------------------------------------------------------------
/server/service/pipeline/BuildDiagnosticsStrategy.ts:
--------------------------------------------------------------------------------
1 | import { IExecuteStrategy } from "@open-sourcerers/j-stillery";
2 | import { Diagnostic, DiagnosticSeverity } from "vscode-languageserver";
3 | import PipelinePayloadModel from "../../model/PipelinePayloadModel";
4 | import { IPmd, IPmdViolation } from "../../model/pmd";
5 | import ILogger from "../logger/ILogger";
6 | import NullLogger from "../logger/NullLogger";
7 |
8 | /**
9 | * Build diagnostic pipeline task strategy
10 | *
11 | * Strategy used to create the pipeline task responsible for building
12 | * the diagnostic object from the parsed PHP mess detector result.
13 | *
14 | * @module vscode-phpmd/service/pipeline
15 | * @author Sandhjé Bouw (sandhje@ecodes.io)
16 | */
17 | class BuildDiagnosticsStrategy implements IExecuteStrategy {
18 | /**
19 | * List of reported PHP mess detector violations
20 | *
21 | * Used to prevent reporting the same violation multiple times.
22 | *
23 | * @property {string[]} reported
24 | */
25 | protected reported: string[] = [];
26 |
27 | /**
28 | * Map of diagnostic severity levels to PHP mess detector severity levels
29 | *
30 | * List indexes are used as the identifiers of the PHP mess detector sevetity level
31 | *
32 | * @property {DiagnosticSeverity[]} severityMap
33 | */
34 | protected severityMap: DiagnosticSeverity[] = [
35 | DiagnosticSeverity.Error,
36 | DiagnosticSeverity.Warning,
37 | DiagnosticSeverity.Information,
38 | DiagnosticSeverity.Hint,
39 | DiagnosticSeverity.Hint
40 | ];
41 |
42 | /**
43 | * Build diagnostics task strategy constructor
44 | *
45 | * @param {ILogger} logger Logger, defaults to Null object implementation
46 | */
47 | public constructor(
48 | private logger: ILogger = new NullLogger()
49 | ) { }
50 |
51 | /**
52 | * Strategy executor
53 | *
54 | * Resolve with a list of diagnostics from parsed PHP mess detector violations.
55 | *
56 | * @see IExecuteStrategy::execute
57 | */
58 | public execute(
59 | input: PipelinePayloadModel,
60 | resolve: (output?: PipelinePayloadModel | PromiseLike) => void,
61 | reject: (reason: any) => void
62 | ) {
63 | let pmd = input.pmd;
64 |
65 | if (pmd === null || typeof pmd.file === "undefined") {
66 | // If no pmd results found, resolve without diagnostics
67 | resolve(input);
68 | return;
69 | }
70 |
71 | // For all files in Pmd result
72 | // Get diagnostics and append to payload
73 | pmd.file.every((file) => {
74 | input.diagnostics = input.diagnostics.concat(this.getDiagnostics(file.$.name, this.getProblems(pmd)));
75 |
76 | return true;
77 | });
78 |
79 | resolve(input);
80 | };
81 |
82 | /**
83 | * Get a list of violations from the parsed PHP mess detector result
84 | *
85 | * @param {IPmd} pmd
86 | * @returns {IPmdViolation[]}
87 | */
88 | protected getProblems(pmd: IPmd): IPmdViolation[] {
89 | return pmd.file[0].violation || [];
90 | }
91 |
92 | /**
93 | * Create a list of diagnostics from a list of PHP mess detector violations
94 | *
95 | * @param {string} filename
96 | * @param {IPmdViolation[]} problems
97 | * @returns {Diagnostic[]}
98 | */
99 | protected getDiagnostics(filename: string, problems: IPmdViolation[]): Diagnostic[] {
100 | let diagnostics: Diagnostic[] = [];
101 |
102 | this.clearReported(); // Clear the already reported diagnostics to prevent false positives upon de-duplication
103 |
104 | problems.forEach((problem) => {
105 | try {
106 | let diagnostic = this.getDiagnostic(problem);
107 |
108 | if (diagnostic !== null) {
109 | diagnostics.push(diagnostic);
110 | }
111 | } catch (e) {
112 | this.logger.error("Error while parsing diagnostic info from PHP mess detector violation.");
113 | }
114 | });
115 |
116 | return diagnostics;
117 | }
118 |
119 | /**
120 | * Create a diagnostic object from a PHP mess detector violation
121 | *
122 | * @throws {Error} If vscode diagnostic object could not be created from PHP mess detector violation
123 | *
124 | * @param {IPmdViolation} problem
125 | * @returns {Diagnostic}
126 | */
127 | protected getDiagnostic(problem: IPmdViolation): Diagnostic {
128 | try {
129 | let line = this.getLine(problem);
130 | let index = this.getIndex(problem);
131 | let length = this.getLength(problem);
132 |
133 | let hash = this.createProblemHash(problem);
134 | if (hash !== null && this.alreadyReported(hash)) {
135 | return null;
136 | }
137 |
138 | this.report(hash);
139 | return {
140 | message: this.getMessage(problem),
141 | range: {
142 | end: {
143 | character: index + length,
144 | line
145 | },
146 | start: {
147 | character: index,
148 | line
149 | },
150 | },
151 | severity: this.getSeverity(problem),
152 | source: this.getSource(problem)
153 | };
154 | } catch (e) {
155 | throw new Error("Unable to create diagnostic (" + e.message + ")");
156 | }
157 | }
158 |
159 | /**
160 | * Add a PHP mess detector violation hash to the reported list to prevent duplicate diagnostics
161 | *
162 | * @param {string} hash
163 | * @returns {void}
164 | */
165 | protected report(hash: string): void {
166 | this.reported.push(hash);
167 | }
168 |
169 | /**
170 | * Check wether the passed violation hash was already reported
171 | *
172 | * @param {string} hash
173 | * @returns {boolean}
174 | */
175 | protected alreadyReported(hash: string): boolean {
176 | return this.reported.indexOf(hash) >= 0;
177 | }
178 |
179 | /**
180 | * Clear the already reported problems
181 | *
182 | * @returns {void}
183 | */
184 | protected clearReported(): void {
185 | this.reported = [];
186 | }
187 |
188 | /**
189 | * Create a problem hash from a PHP mess detector violation
190 | *
191 | * @param {IPmdViolation} problem
192 | * @returns {string}
193 | */
194 | protected createProblemHash(problem: IPmdViolation): string {
195 | let ruleset = problem.$.ruleset || null;
196 | let rule = problem.$.rule || null;
197 | let line = problem.$.beginline || null;
198 |
199 | let str = ruleset + "-" + rule + "-" + line;
200 |
201 | let hash = 0;
202 | if (str.length === 0) {
203 | return null;
204 | }
205 |
206 | for (let i = 0; i < str.length; i++) {
207 | let char = str.charCodeAt(i);
208 | hash = ((hash << 5) - hash) + char;
209 | hash = hash & hash; // Convert to 32bit integer
210 | }
211 |
212 | return hash.toString();
213 | }
214 |
215 | /**
216 | * Get the violation line number from a PHP mess detector violation
217 | *
218 | * @throws {Error} If line number could not be extracted from violation
219 | *
220 | * @param {IPmdViolation} problem
221 | * @return {number}
222 | */
223 | protected getLine(problem: IPmdViolation): number {
224 | let beginline = problem.$.beginline || null;
225 |
226 | if (beginline === null) {
227 | throw new Error("Unable to find problem begin line");
228 | }
229 |
230 | return parseInt(beginline, 10) - 1;
231 | }
232 |
233 | /**
234 | * Get the violation start index from a PHP mess detector violation
235 | *
236 | * Returns a fixed value of 0 since PHP mess detector violations do not contain a start index.
237 | *
238 | * @param {IPmdViolation} problem
239 | * @return {number}
240 | */
241 | protected getIndex(problem: IPmdViolation): number {
242 | return 0;
243 | }
244 |
245 | /**
246 | * Get the violation length from a PHP mess detector violation
247 | *
248 | * Returns a fixed value of Number.MAX_VALUE since PHP mess detector violations do not contain a length.
249 | *
250 | * @param {IPmdViolation} problem
251 | * @return {number}
252 | */
253 | protected getLength(problem: IPmdViolation): number {
254 | return Number.MAX_VALUE;
255 | }
256 |
257 | /**
258 | * Get the violation message from a PHP mess detector violation
259 | *
260 | * @throws {Error} If message could not be extracted from violation
261 | *
262 | * @param {IPmdViolation} problem
263 | * @return {string}
264 | */
265 | protected getMessage(problem: IPmdViolation): string {
266 | let message = problem._ || null;
267 |
268 | message = message.replace(/^\s+|\s+$/g, '');
269 |
270 | if (message === null) {
271 | throw new Error("Unable to find problem message");
272 | }
273 |
274 | return message;
275 | }
276 |
277 | /**
278 | * Get the source from a PHP mess detector violation
279 | *
280 | * Returns a fixed value of "PHP Mess Detector" since PHP mess detector violations do not contain a source.
281 | *
282 | * @param {IPmdViolation} problem
283 | * @return {number}
284 | */
285 | protected getSource(problem: IPmdViolation): string {
286 | return "PHP Mess Detector";
287 | }
288 |
289 | /**
290 | * Get the violation severity from a PHP mess detector violation
291 | *
292 | * Maps PHP mess detector severities to vscode diagnostic severities using the severityMap property. If no
293 | * mapping was possible defaults to a severity of "Hint".
294 | *
295 | * @param {IPmdViolation} problem
296 | * @return {DiagnosticSeverity}
297 | */
298 | protected getSeverity(problem: any): DiagnosticSeverity {
299 | let priority: number = parseInt(problem.$.priority, 10) || 100;
300 |
301 | let severity = this.severityMap[priority - 1] || DiagnosticSeverity.Hint;
302 |
303 | return severity;
304 | }
305 | }
306 |
307 | export default BuildDiagnosticsStrategy;
308 |
--------------------------------------------------------------------------------
/server/service/pipeline/ExecuteProcessStrategy.ts:
--------------------------------------------------------------------------------
1 | import { IExecuteStrategy } from "@open-sourcerers/j-stillery";
2 | import * as Process from "child_process";
3 | import PipelineErrorModel from "../../model/PipelineErrorModel";
4 | import PipelinePayloadModel from "../../model/PipelinePayloadModel";
5 | import ILogger from "../logger/ILogger";
6 | import NullLogger from "../logger/NullLogger";
7 | import PhpmdService from "../PhpmdService";
8 | import PhpmdCommandBuilder from "../PhpmdCommandBuilder";
9 |
10 | /**
11 | * Execute PHP mess detector pipeline task strategy
12 | *
13 | * Strategy used to create the pipeline task responsible for executing
14 | * the PHP mess detector command.
15 | *
16 | * @module vscode-phpmd/service/pipeline
17 | * @author Sandhjé Bouw (sandhje@ecodes.io)
18 | */
19 | class ExecuteProcessStrategy implements IExecuteStrategy {
20 | /**
21 | * PHP mess detector service
22 | *
23 | * @property {PhpmdService} service
24 | */
25 | private service: PhpmdService;
26 |
27 | /**
28 | * Execute process task strategy constructor
29 | *
30 | * @param {PhpmdCommandBuilder} commandBuilder CommandBuilder used to instantiate the PHP mess detector service
31 | * @param {string} rules PHP mess detector rules to be passed as option to the command
32 | * @param {ILogger} logger Logger, defaults to Null object implementation
33 | */
34 | public constructor(
35 | private commandBuilder: PhpmdCommandBuilder,
36 | private rules: string,
37 | private logger: ILogger = new NullLogger()
38 | ) { }
39 |
40 | /**
41 | * Strategy executor
42 | *
43 | * Resolve with the result from the executed PHP mess detector command.
44 | *
45 | * @see IExecuteStrategy::execute
46 | */
47 | public execute(
48 | input: PipelinePayloadModel,
49 | resolve: (output?: PipelinePayloadModel | PromiseLike) => void,
50 | reject: (reason: any) => void
51 | ) {
52 | this.executeProcess(input.path).then((data) => {
53 | input.raw = this.ltrimXML(data);
54 |
55 | resolve(input);
56 | }, (err: Error) => {
57 | reject(new PipelineErrorModel(err, false));
58 | });
59 | };
60 |
61 | /**
62 | * PHP mess detector service setter
63 | *
64 | * Allows overriding of the PHP mess detector service for testing purposes.
65 | *
66 | * @param {PhpmdService} service
67 | */
68 | public setService(service: PhpmdService) {
69 | this.service = service;
70 | }
71 |
72 | /**
73 | * PHP mess detector service getter
74 | *
75 | * Creates an instance of the default PHP mess detector service if no other instance was set before.
76 | *
77 | * @returns {PhpmdService}
78 | */
79 | protected getService() {
80 | if (!this.service) {
81 | this.service = new PhpmdService(this.commandBuilder);
82 | this.service.setLogger(this.logger);
83 | }
84 |
85 | return this.service;
86 | }
87 |
88 | /**
89 | * Strip any leading text or whitespace before the xml opening tag
90 | *
91 | * @param {string} xml
92 | * @returns {string}
93 | */
94 | protected ltrimXML(xml: string): string {
95 | return xml.substring(xml.indexOf("}
103 | */
104 | protected executeProcess(path: string): Promise {
105 | return this.getService().run(`"${path}" xml "${this.rules}"`);
106 | }
107 | }
108 |
109 | export default ExecuteProcessStrategy;
110 |
--------------------------------------------------------------------------------
/server/service/pipeline/ParseStrategy.ts:
--------------------------------------------------------------------------------
1 | import { IExecuteStrategy } from "@open-sourcerers/j-stillery";
2 | import { Parser } from "xml2js";
3 | import PipelinePayloadModel from "../../model/PipelinePayloadModel";
4 | import ILogger from "../logger/ILogger";
5 | import { IPmd } from "../../model/pmd";
6 |
7 | /**
8 | * Parse pipeline task strategy
9 | *
10 | * Strategy used to create the pipeline task responsible for parsing
11 | * the PHP mess detector result.
12 | *
13 | * @module vscode-phpmd/service/pipeline
14 | * @author Sandhjé Bouw (sandhje@ecodes.io)
15 | */
16 | class ParseStrategy implements IExecuteStrategy {
17 | /**
18 | * Parse task strategy constructor
19 | *
20 | * @param {Parser} parser
21 | * @param {ILogger} logger
22 | */
23 | public constructor(
24 | private parser: Parser,
25 | private logger: ILogger
26 | ) { }
27 |
28 | /**
29 | * Strategy executor
30 | *
31 | * Resolve with the parse PHP mess detector result.
32 | *
33 | * @see IExecuteStrategy::execute
34 | */
35 | public execute(
36 | input: PipelinePayloadModel,
37 | resolve: (output?: PipelinePayloadModel | PromiseLike) => void,
38 | reject: (reason: any) => void
39 | ) {
40 | this.parser.parseString(this.escapeInput(input), (error, result) => {
41 | if (error) {
42 | this.logger.log(`Unable to parse result xml. ${error}`);
43 | }
44 |
45 | if (!result) {
46 | result = { pmd: null };
47 | }
48 |
49 | input.pmd = result.pmd;
50 |
51 | resolve(input);
52 | });
53 | };
54 |
55 | private escapeInput(input: PipelinePayloadModel): string {
56 | const escapedPath = input.path.replace(/[<>&'"]/g, function (c) {
57 | switch (c) {
58 | case '<': return '<';
59 | case '>': return '>';
60 | case '&': return '&';
61 | case '\'': return ''';
62 | case '"': return '"';
63 | }
64 | });
65 |
66 | if (escapedPath !== input.path) {
67 | this.logger.info(`Escaping path in phpmd output xml to prevent parsing errors. Replacing ${input.path} with ${escapedPath}`, true);
68 |
69 | const trimmedPath = /^[a-zA-Z]:/.test(input.path) ? input.path.substr(2) : input.path;
70 | const trimmedEscapedPath = /^[a-zA-Z]:/.test(escapedPath) ? escapedPath.substr(2) : escapedPath;
71 |
72 | return input.raw.replace(trimmedPath, trimmedEscapedPath);
73 | }
74 |
75 | return input.raw;
76 | }
77 | }
78 |
79 | export default ParseStrategy;
80 |
--------------------------------------------------------------------------------
/server/service/pipeline/TestFileStrategy.ts:
--------------------------------------------------------------------------------
1 | import { IExecuteStrategy } from "@open-sourcerers/j-stillery";
2 | import * as fs from "fs";
3 | import PipelineErrorModel from "../../model/PipelineErrorModel";
4 | import PipelinePayloadModel from "../../model/PipelinePayloadModel";
5 | import ILogger from "../logger/ILogger";
6 | import NullLogger from "../logger/NullLogger";
7 |
8 | /**
9 | * Test file pipeline task strategy
10 | *
11 | * Strategy used to test wether the file to be analyzed by PHPMD is readable
12 | *
13 | * @module vscode-phpmd/service/pipeline
14 | * @author Sandhjé Bouw (sandhje@ecodes.io)
15 | */
16 | class TestFileStrategy implements IExecuteStrategy {
17 | /**
18 | * Test file task strategy constructor
19 | *
20 | * @param {IReadFile} readfile
21 | * @param {ILogger} logger
22 | */
23 | public constructor(
24 | private readfile: IReadFile,
25 | private logger: ILogger = new NullLogger()
26 | ) { }
27 |
28 | /**
29 | * Strategy executor
30 | *
31 | * Test wether the file is in the input is readable
32 | *
33 | * @see IExecuteStrategy::execute
34 | */
35 | public execute(
36 | input: PipelinePayloadModel,
37 | resolve: (output?: PipelinePayloadModel | PromiseLike) => void,
38 | reject: (reason: any) => void
39 | ) {
40 | this.readfile(input.path, "utf8", (err: NodeJS.ErrnoException, data: string) => {
41 | // Reject the pipeline if the file was not found or is not readable
42 | if (err) {
43 | this.logger.error("File " + input.path + " not found");
44 | return reject(new PipelineErrorModel(err, true));
45 | }
46 |
47 | // Resolve with unmodified input if the file is readable
48 | this.logger.info("File " + input.path + " test successful", true);
49 | return resolve(input);
50 | });
51 | };
52 | }
53 |
54 | /**
55 | * Readfile interface
56 | *
57 | * @module vscode-phpmd/service/pipeline
58 | * @author Sandhjé Bouw (sandhje@ecodes.io)
59 | */
60 | interface IReadFile {
61 | (filename: string, encoding: string, callback: (err: NodeJS.ErrnoException, data: string) => void): void;
62 | }
63 |
64 | export default TestFileStrategy;
65 |
--------------------------------------------------------------------------------
/tests/controller/PhpmdControllerTest.ts:
--------------------------------------------------------------------------------
1 | import { Pipeline } from "@open-sourcerers/j-stillery";
2 | import { assert, expect } from "chai";
3 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
4 | import * as sinon from "sinon";
5 | import { Diagnostic, IConnection, Position, Range, TextDocumentIdentifier, WorkspaceFolder } from "vscode-languageserver";
6 | import PhpmdController from "../../server/controller/PhpmdController";
7 | import PipelinePayloadFactory from "../../server/factory/PipelinePayloadFactory";
8 | import IPhpmdSettingsModel from "../../server/model/IPhpmdSettingsModel";
9 | import PipelineErrorModel from "../../server/model/PipelineErrorModel";
10 | import PipelinePayloadModel from "../../server/model/PipelinePayloadModel";
11 | import NullLogger from "../../server/service/logger/NullLogger";
12 | import NullNotifier from "../../server/service/notifier/NullNotifier";
13 | import PhpmdService from "../../server/service/PhpmdService";
14 | import IPhpmdEnvironmentModel from "../../server/model/IPhpmdEnvironmentModel";
15 |
16 | @suite("PhpMD controller")
17 | class PhpmdControllerTest {
18 |
19 | @test("Should send diagnostics")
20 | public assertSendDiagnostics(done) {
21 | // Arrange
22 | // =======
23 | // Fake settings
24 | let settings = {
25 | enabled: true,
26 | command: "test",
27 | rules: "cleancode,codesize,controversial,design,unusedcode,naming",
28 | verbose: true
29 | };
30 |
31 | // Fake environment
32 | let environment = {
33 | homeDir: "/home/JohnDoe",
34 | workspaceFolders: [
35 | {
36 | name: "www",
37 | uri: "file://var/www"
38 | }
39 | ]
40 | };
41 |
42 | // Fake document
43 | let document = {
44 | uri: "test"
45 | };
46 |
47 | // Fake diagnostic
48 | let diagnostic = {};
49 | diagnostic.range = {
50 | end: {
51 | character: Number.MAX_VALUE,
52 | line: 0
53 | },
54 | start: {
55 | character: 0,
56 | line: 0
57 | }
58 | };
59 | diagnostic.severity = 1;
60 | diagnostic.code = null;
61 | diagnostic.source = "Test";
62 | diagnostic.message = "Lorem ipsum dolor";
63 |
64 | // GetVersion stub
65 | let testPhpmdStub = sinon.stub();
66 | testPhpmdStub.returns(Promise.resolve(true));
67 |
68 | // Fake service
69 | let service = {};
70 | service.testPhpmd = testPhpmdStub;
71 |
72 | // Fake pipeline payload
73 | let payload = {};
74 | payload.uri = document.uri;
75 | payload.diagnostics = [diagnostic];
76 |
77 | // Stub connection
78 | let connection = {};
79 | connection.sendDiagnostics = (params) => {
80 | // Assert
81 | // ======
82 | // Expect params to match fakes
83 | expect(params.uri).to.equal(document.uri);
84 | expect(params.diagnostics).to.equal(payload.diagnostics);
85 | };
86 |
87 | // Stub pipeline payload factory
88 | let pipelinePayloadFactory = {};
89 | pipelinePayloadFactory.setUri = (uri: string) => { return pipelinePayloadFactory; };
90 | pipelinePayloadFactory.create = () => { return payload; };
91 |
92 | // Stub pipeline
93 | let pipeline = new Pipeline();
94 |
95 | // Create and configure controller
96 | let controller = new PhpmdController(connection, settings, environment);
97 | controller.setPipeline(pipeline);
98 | controller.setPipelinePayloadFactory(pipelinePayloadFactory);
99 | controller.setLogger(new NullLogger());
100 | controller.setService(service);
101 |
102 | // Act
103 | // ===
104 | controller.validate(document).then((result) => {
105 | // Assert
106 | // ======
107 | // Expect result to equal true
108 | expect(result).to.equal(true);
109 | done();
110 | });
111 | }
112 |
113 | @test("Should test PHPMD command")
114 | public assertTestPhpmd(done) {
115 | // Arrange
116 | // =======
117 | // Fake settings
118 | let settings = {
119 | enabled: true,
120 | command: "test",
121 | rules: "cleancode,codesize,controversial,design,unusedcode,naming",
122 | verbose: true
123 | };
124 |
125 | // Fake environment
126 | let environment = {
127 | homeDir: "/home/JohnDoe",
128 | workspaceFolders: [
129 | {
130 | name: "www",
131 | uri: "file://var/www"
132 | }
133 | ]
134 | };
135 |
136 | // Fake document
137 | let document = {
138 | uri: "test"
139 | };
140 |
141 | // GetVersion stub
142 | let testPhpmdStub = sinon.stub();
143 | testPhpmdStub.returns(Promise.reject(Error("Test error")));
144 |
145 | // Fake service
146 | let service = {};
147 | service.testPhpmd = testPhpmdStub;
148 |
149 | // Stub connection
150 | let connection = {};
151 |
152 | // Create and configure controller
153 | let controller = new PhpmdController(connection, settings, environment);
154 | controller.setLogger(new NullLogger());
155 | controller.setNotifier(new NullNotifier());
156 | controller.setService(service);
157 |
158 | // Act
159 | // ===
160 | controller.validate(document).then(null, (err: Error) => {
161 | // Assert
162 | // ======
163 | expect(err.message).to.equal("Test error");
164 | done();
165 | });
166 | }
167 |
168 | @test("Should reject on error in pipeline")
169 | public assertRejectOnError(done) {
170 | // Arrange
171 | // =======
172 | // Fake settings
173 | let settings = {
174 | enabled: true,
175 | command: "test",
176 | rules: "cleancode,codesize,controversial,design,unusedcode,naming",
177 | verbose: true
178 | };
179 |
180 | // Fake environment
181 | let environment = {
182 | homeDir: "/home/JohnDoe",
183 | workspaceFolders: [
184 | {
185 | name: "www",
186 | uri: "file://var/www"
187 | }
188 | ]
189 | };
190 |
191 | // Fake document
192 | let document = {
193 | uri: "test"
194 | };
195 |
196 | // GetVersion stub
197 | let testPhpmdStub = sinon.stub();
198 | testPhpmdStub.returns(Promise.resolve(true));
199 |
200 | // Fake service
201 | let service = {};
202 | service.testPhpmd = testPhpmdStub;
203 |
204 | // Fake pipeline payload
205 | let payload = {};
206 | payload.uri = document.uri;
207 |
208 | // Stub connection
209 | let connection = {};
210 |
211 | // Stub pipeline payload factory
212 | let pipelinePayloadFactory = {};
213 | pipelinePayloadFactory.setUri = (uri: string) => { return pipelinePayloadFactory; };
214 | pipelinePayloadFactory.create = () => { return payload; };
215 |
216 | // Stub pipeline run
217 | let runStub = sinon.stub();
218 | runStub.rejects(new PipelineErrorModel("Test error"));
219 |
220 | // Stub pipeline
221 | let pipeline = new Pipeline();
222 | pipeline.run = runStub;
223 |
224 | // Create and configure controller
225 | let controller = new PhpmdController(connection, settings, environment);
226 | controller.setPipeline(pipeline);
227 | controller.setPipelinePayloadFactory(pipelinePayloadFactory);
228 | controller.setService(service);
229 |
230 | // Act
231 | // ===
232 | controller.validate(document).then(null, (err: PipelineErrorModel) => {
233 | // Assert
234 | // ======
235 | expect(err).to.equal("Test error");
236 | done();
237 | });
238 | }
239 |
240 | @test("Should not check if disabled")
241 | public assertExtensionDisabled(done) {
242 | // Arrange
243 | // =======
244 | // Fake settings
245 | let settings = {
246 | enabled: false,
247 | command: "test",
248 | rules: "cleancode,codesize,controversial,design,unusedcode,naming",
249 | verbose: true
250 | };
251 |
252 | // Fake environment
253 | let environment = {
254 | homeDir: "/home/JohnDoe",
255 | workspaceFolders: [
256 | {
257 | name: "www",
258 | uri: "file://var/www"
259 | }
260 | ]
261 | };
262 |
263 | // Fake document
264 | let document = {
265 | uri: "test"
266 | };
267 |
268 | // Stub connection
269 | let connection = {};
270 | connection.sendDiagnostics = sinon.spy();
271 |
272 | // Create and configure controller
273 | let controller = new PhpmdController(connection, settings, environment);
274 |
275 | // Act
276 | // ===
277 | controller.validate(document).then(() => {
278 | // Assert
279 | // ======
280 | expect((connection.sendDiagnostics).notCalled).to.be.true;
281 | done();
282 | });
283 | }
284 |
285 | @test("Should clear diagnostics")
286 | public assertClear(done) {
287 | // Arrange
288 | // =======
289 | // Fake settings
290 | let settings = {
291 | enabled: true,
292 | command: "test",
293 | rules: "cleancode,codesize,controversial,design,unusedcode,naming",
294 | verbose: true,
295 | clearOnClose: true
296 | };
297 |
298 | // Fake environment
299 | let environment = {
300 | homeDir: "/home/JohnDoe",
301 | workspaceFolders: [
302 | {
303 | name: "www",
304 | uri: "file://var/www"
305 | }
306 | ]
307 | };
308 |
309 | // Fake document
310 | let document = {
311 | uri: "test"
312 | };
313 |
314 | // Stub connection
315 | let connection = {};
316 | connection.sendDiagnostics = (diagnosticParams => {
317 | // Assert
318 | // ======
319 | expect(diagnosticParams.diagnostics).to.be.empty;
320 | done();
321 | });
322 |
323 | // Create and configure controller
324 | let controller = new PhpmdController(connection, settings, environment);
325 | controller.setLogger(new NullLogger());
326 |
327 | // Act
328 | // ===
329 | controller.clear(document);
330 | }
331 |
332 |
333 | @test("Should not clear diagnostics if disabled in settings")
334 | public assertClearDisabled() {
335 | // Arrange
336 | // =======
337 | // Fake settings
338 | let settings = {
339 | enabled: true,
340 | command: "test",
341 | rules: "cleancode,codesize,controversial,design,unusedcode,naming",
342 | verbose: true,
343 | clearOnClose: false
344 | };
345 |
346 | // Fake environment
347 | let environment = {
348 | homeDir: "/home/JohnDoe",
349 | workspaceFolders: [
350 | {
351 | name: "www",
352 | uri: "file://var/www"
353 | }
354 | ]
355 | };
356 |
357 | // Fake document
358 | let document = {
359 | uri: "test"
360 | };
361 |
362 | // Stub connection
363 | let connection = {};
364 | connection.sendDiagnostics = sinon.spy();
365 |
366 | // Create and configure controller
367 | let controller = new PhpmdController(connection, settings, environment);
368 | controller.setLogger(new NullLogger());
369 |
370 | // Act
371 | // ===
372 | controller.clear(document);
373 |
374 | // Assert
375 | expect((connection.sendDiagnostics).notCalled).to.be.true;
376 | }
377 | };
378 |
--------------------------------------------------------------------------------
/tests/factory/BuildDiagnosticsTaskFactoryTest.ts:
--------------------------------------------------------------------------------
1 | import { Task } from "@open-sourcerers/j-stillery";
2 | import { assert, expect } from "chai";
3 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
4 | import * as sinon from "sinon";
5 | import BuildDiagnosticsTaskFactory from "../../server/factory/BuildDiagnosticsTaskFactory";
6 | import IPhpmdSettingsModel from "../../server/model/IPhpmdSettingsModel";
7 |
8 | @suite("BuildDiagnosticsTask factory")
9 | class BuildDiagnosticsTaskFactoryTest {
10 |
11 | @test("Should create BuildDiagnosticTask instance")
12 | public assertCreate() {
13 | // Arrange
14 | // =======
15 | // Fake settings
16 | let settings = {};
17 |
18 | // Create and configure factory instance
19 | let factory = new BuildDiagnosticsTaskFactory(settings);
20 |
21 | // Act
22 | let task = factory.create();
23 |
24 | // Assert
25 | expect(task).to.be.instanceof(Task);
26 | }
27 | }
--------------------------------------------------------------------------------
/tests/factory/ClientConnectionNotifierFactoryTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import { IConnection } from "vscode-languageserver";
5 | import ClientConnectionNotifierFactory from "../../server/factory/ClientConnectionNotifierFactory";
6 | import ClientConnectionNotifier from "../../server/service/notifier/ClientConnectionNotifier";
7 |
8 | @suite("Client connection notifier factory")
9 | class ClientConnectionNotifierFactoryTest {
10 |
11 | @test("Should create ClientConnectionNotifier instance")
12 | public assertCreate() {
13 | // Arrange
14 | // =======
15 | // Fake connection
16 | let connection = {};
17 |
18 | let factory = new ClientConnectionNotifierFactory();
19 | factory.setConnection(connection);
20 |
21 | // Act
22 | let notifier = factory.create();
23 |
24 | // Assert
25 | expect(notifier).to.be.instanceof(ClientConnectionNotifier);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/factory/ExecuteProcessTaskFactoryTest.ts:
--------------------------------------------------------------------------------
1 | import { Task } from "@open-sourcerers/j-stillery";
2 | import { assert, expect } from "chai";
3 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
4 | import * as sinon from "sinon";
5 | import ExecuteProcessTaskFactory from "../../server/factory/ExecuteProcessTaskFactory";
6 | import IPhpmdSettingsModel from "../../server/model/IPhpmdSettingsModel";
7 | import IPhpmdEnvironmentModel from "../../server/model/IPhpmdEnvironmentModel";
8 | import { WorkspaceFolder } from "vscode-languageserver";
9 |
10 | @suite("ExecuteProcessTask factory")
11 | class ExecuteProcessTaskFactoryTest {
12 |
13 | @test("Should create ExecuteProcessTask instance")
14 | public assertCreate() {
15 | // Arrange
16 | // =======
17 | // Fake settings
18 | let settings = {
19 | command: "test",
20 | rules: "test",
21 | verbose: true
22 | };
23 |
24 | // Fake environment
25 | let environment = {
26 | homeDir: "/home/JohnDoe",
27 | workspaceFolders: [
28 | {
29 | name: "www",
30 | uri: "file://var/www"
31 | }
32 | ]
33 | };
34 |
35 | // Create and configure factory instance
36 | let factory = new ExecuteProcessTaskFactory(settings, environment);
37 |
38 | // Act
39 | let task = factory.create();
40 |
41 | // Assert
42 | expect(task).to.be.instanceof(Task);
43 | }
44 | }
--------------------------------------------------------------------------------
/tests/factory/NullLoggerFactoryTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import { IConnection } from "vscode-languageserver";
5 | import NullLoggerFactory from "../../server/factory/NullLoggerFactory";
6 | import NullLogger from "../../server/service/logger/NullLogger";
7 |
8 | @suite("Null logger factory")
9 | class NullLoggerFactoryTest {
10 |
11 | @test("Should create NullLogger instance")
12 | public assertCreate() {
13 | // Arrange
14 | // =======
15 | // Fake connection
16 | let connection = {};
17 |
18 | // Create factory instance and configure
19 | let factory = new NullLoggerFactory();
20 | factory.setConnection(connection);
21 | factory.setVerbose(false);
22 |
23 | // Act
24 | let logger = factory.create();
25 |
26 | // Assert
27 | expect(logger).to.be.instanceof(NullLogger);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/factory/NullNotifierFactoryTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import NullNotifierFactory from "../../server/factory/NullNotifierFactory";
5 | import NullNotifier from "../../server/service/notifier/NullNotifier";
6 |
7 | @suite("Null notifier factory")
8 | class NullNotifierFactoryTest {
9 |
10 | @test("Should create NullNotifier instance")
11 | public assertCreate() {
12 | // Arrange
13 | let factory = new NullNotifierFactory();
14 |
15 | // Act
16 | let notifier = factory.create();
17 |
18 | // Assert
19 | expect(notifier).to.be.instanceof(NullNotifier);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/factory/ParseTaskFactoryTest.ts:
--------------------------------------------------------------------------------
1 | import { Task } from "@open-sourcerers/j-stillery";
2 | import { assert, expect } from "chai";
3 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
4 | import * as sinon from "sinon";
5 | import ParseTaskFactory from "../../server/factory/ParseTaskFactory";
6 | import IPhpmdSettingsModel from "../../server/model/IPhpmdSettingsModel";
7 |
8 | @suite("ParseTask factory")
9 | class ParseTaskFactoryTest {
10 |
11 | @test("Should create ParseTask instance")
12 | public assertCreate() {
13 | // Arrange
14 | // =======
15 | // Fake settings
16 | let settings = {
17 | command: "test",
18 | rules: "test",
19 | verbose: true
20 | };
21 |
22 | // Create and configure factory instance
23 | let factory = new ParseTaskFactory(settings);
24 |
25 | // Act
26 | let task = factory.create();
27 |
28 | // Assert
29 | expect(task).to.be.instanceof(Task);
30 | }
31 | }
--------------------------------------------------------------------------------
/tests/factory/PhpmdControllerFactoryTest.ts:
--------------------------------------------------------------------------------
1 | import { Task } from "@open-sourcerers/j-stillery";
2 | import { assert, expect } from "chai";
3 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
4 | import * as sinon from "sinon";
5 | import { IConnection } from "vscode-languageserver";
6 | import PhpmdController from "../../server/controller/PhpmdController";
7 | import PhpmdControllerFactory from "../../server/factory/PhpmdControllerFactory";
8 | import IPhpmdSettingsModel from "../../server/model/IPhpmdSettingsModel";
9 |
10 | @suite("PhpmdController factory")
11 | class PhpmdControllerFactoryTest {
12 |
13 | @test("Should create PhpmdController instance")
14 | public assertCreate() {
15 | // Arrange
16 | // =======
17 | // Fake connnection
18 | let connection = {};
19 |
20 | // Fake settings
21 | let settings = {};
22 |
23 | // Create and configure factory instance
24 | let factory = new PhpmdControllerFactory();
25 | factory.setConnection(connection);
26 | factory.setSettings(settings);
27 |
28 | // Act
29 | let controller = factory.create();
30 |
31 | // Assert
32 | expect(controller).to.be.instanceof(PhpmdController);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tests/factory/PipelineFactoryTest.ts:
--------------------------------------------------------------------------------
1 | import { Pipeline } from "@open-sourcerers/j-stillery";
2 | import { assert, expect } from "chai";
3 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
4 | import * as sinon from "sinon";
5 | import { IConnection } from "vscode-languageserver";
6 | import PipelineFactory from "../../server/factory/PipelineFactory";
7 | import IPhpmdSettingsModel from "../../server/model/IPhpmdSettingsModel";
8 | import IPhpmdEnvironmentModel from "../../server/model/IPhpmdEnvironmentModel";
9 |
10 | @suite("Pipeline factory")
11 | class PipelineFactoryTest {
12 |
13 | @test("Should create Pipeline instance")
14 | public assertCreate() {
15 | // Arrange
16 | // =======
17 | // Fake settings
18 | let settings = {};
19 |
20 | // Fake environment
21 | let environment = {};
22 |
23 | // Create and configure factory instance
24 | let factory = new PipelineFactory(settings, environment);
25 |
26 | // Act
27 | let pipeline = factory.create();
28 |
29 | // Assert
30 | expect(pipeline).to.be.instanceof(Pipeline);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/factory/PipelinePayloadFactoryTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import PipelinePayloadFactory from "../../server/factory/PipelinePayloadFactory";
5 | import PipelinePayloadModel from "../../server/model/PipelinePayloadModel";
6 |
7 | @suite("PipelinePayload factory")
8 | class PipelineFactoryTest {
9 |
10 | @test("Should create PipelinePayload instance")
11 | public assertCreate() {
12 | // Arrange
13 | // =======
14 | // Fake settings
15 | let uri = "Test";
16 |
17 | // Create and configure factory instance
18 | let factory = new PipelinePayloadFactory(uri);
19 |
20 | // Act
21 | let pipelinePayload = factory.create();
22 |
23 | // Assert
24 | expect(pipelinePayload).to.be.instanceof(PipelinePayloadModel);
25 | expect(pipelinePayload.uri).to.equal(uri);
26 | }
27 |
28 | @test("Should update uri on setter")
29 | public assertSetUri() {
30 | // Arrange
31 | // =======
32 | // Fake settings
33 | let uri = "Test1";
34 | let uri2 = "Test2";
35 |
36 | // Create and configure factory instance
37 | let factory = new PipelinePayloadFactory(uri);
38 | factory.setUri(uri2);
39 |
40 | // Act
41 | let pipelinePayload = factory.create();
42 |
43 | // Assert
44 | expect(pipelinePayload).to.be.instanceof(PipelinePayloadModel);
45 | expect(pipelinePayload.uri).to.equal(uri2);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/factory/RemoteConsoleLoggerFactoryTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import { IConnection } from "vscode-languageserver";
5 | import RemoteConsoleLoggerFactory from "../../server/factory/RemoteConsoleLoggerFactory";
6 | import ILogger from "../../server/service/logger/ILogger";
7 | import RemoteConsoleLogger from "../../server/service/logger/RemoteConsoleLogger";
8 |
9 | @suite("RemoteConsoleLogger factory")
10 | class RemoteConsoleLoggerFactoryTest {
11 |
12 | @test("Should create RemoteConsoleLogger instance")
13 | public assertCreate() {
14 | // Arrange
15 | // =======
16 | // Fake connection
17 | let connection = {};
18 | connection.console = {};
19 |
20 | // Create factory instance and configure
21 | let factory = new RemoteConsoleLoggerFactory();
22 | factory.setConnection(connection);
23 | factory.setVerbose(false);
24 |
25 | // Act
26 | let logger = factory.create();
27 |
28 | // Assert
29 | expect(logger).to.be.instanceof(RemoteConsoleLogger);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/tests/factory/TestFileTaskFactoryTest.ts:
--------------------------------------------------------------------------------
1 | import { Task } from "@open-sourcerers/j-stillery";
2 | import { assert, expect } from "chai";
3 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
4 | import * as sinon from "sinon";
5 | import TestFileTaskFactory from "../../server/factory/TestFileTaskFactory";
6 | import IPhpmdSettingsModel from "../../server/model/IPhpmdSettingsModel";
7 |
8 | @suite("TestFileTask factory")
9 | class TestFileTaskFactoryTest {
10 |
11 | @test("Should create TestFileTask instance")
12 | public assertCreate() {
13 | // Arrange
14 | // =======
15 | // Fake settings
16 | let settings = {
17 | command: "test",
18 | rules: "test",
19 | verbose: true
20 | };
21 |
22 | // Create and configure factory instance
23 | let factory = new TestFileTaskFactory(settings);
24 |
25 | // Act
26 | let task = factory.create();
27 |
28 | // Assert
29 | expect(task).to.be.instanceof(Task);
30 | }
31 | }
--------------------------------------------------------------------------------
/tests/model/PipelinePayloadModelTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import { URI } from "vscode-uri";
5 | import PipelinePayloadModel from "../../server/model/PipelinePayloadModel";
6 |
7 | @suite("Pipeline payload model")
8 | class PipelinePayloadModelTest {
9 |
10 | @test("Get path from uri")
11 | public assertPath() {
12 | // Arrange
13 | // =======
14 | // Fake uri
15 | let uri = "file:///c%3A/Projects/Untitled-2.php";
16 |
17 | // Model
18 | let model = new PipelinePayloadModel(uri);
19 |
20 | // Act
21 | let path = model.path;
22 |
23 | // Assert
24 | expect(path).to.equal(URI.parse(uri).fsPath);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/service/PhpmdCommandBuilderTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import PhpmdCommandBuilder from "../../server/service/PhpmdCommandBuilder";
5 | import { URI } from "vscode-uri";
6 | import { WorkspaceFolder } from "vscode-languageserver";
7 |
8 | @suite("PhpMD Command Builder")
9 | class PhpmdCommandBuilderTest {
10 |
11 | @test("Should return true if using global php")
12 | public assertUsingGlobalPhp() {
13 | // Arrange
14 | const command = "php /path/to/phpmd.phar";
15 | const unsafeCommand = "";
16 | const unsafeCommandEnabled = false;
17 | const workspaceFolders = [];
18 | const homeDir = "";
19 |
20 | // Act
21 | const commandBuilder = new PhpmdCommandBuilder(command, unsafeCommandEnabled, unsafeCommand, workspaceFolders, homeDir);
22 |
23 | // Assert
24 | expect(commandBuilder.usingGlobalPhp()).to.be.true;
25 | }
26 |
27 | @test("Should return false if not using global php")
28 | public assertNotUsingGlobalPhp() {
29 | // Arrange
30 | const command = "/path/to/php /path/to/phpmd.phar";
31 | const unsafeCommand = "";
32 | const unsafeCommandEnabled = false;
33 | const workspaceFolders = [];
34 | const homeDir = "";
35 |
36 | // Act
37 | const commandBuilder = new PhpmdCommandBuilder(command, unsafeCommandEnabled, unsafeCommand, workspaceFolders, homeDir);
38 |
39 | // Assert
40 | expect(commandBuilder.usingGlobalPhp()).to.be.false;
41 | }
42 |
43 | @test("Should build phpmd version command")
44 | public assertBuildVersionCommand() {
45 | // Arrange
46 | const command = "php /path/to/phpmd.phar";
47 | const unsafeCommand = "";
48 | const unsafeCommandEnabled = false;
49 | const workspaceFolders = [];
50 | const homeDir = "";
51 |
52 | // Act
53 | const commandBuilder = new PhpmdCommandBuilder(command, unsafeCommandEnabled, unsafeCommand, workspaceFolders, homeDir);
54 |
55 | // Assert
56 | expect(commandBuilder.buildPhpmdVersionCommand()).to.equal("php /path/to/phpmd.phar --version");
57 | }
58 |
59 | @test("Should build phpmd command")
60 | public assertBuildPhpMDCommand() {
61 | // Arrange
62 | const command = "php /path/to/phpmd.phar";
63 | const unsafeCommand = "";
64 | const unsafeCommandEnabled = false;
65 | const workspaceFolders = [];
66 | const homeDir = "";
67 | const options = `"/path/to/file.php" xml "cleancode,codesize,controversial,design,unusedcode,naming"`;
68 |
69 | // Act
70 | const commandBuilder = new PhpmdCommandBuilder(command, unsafeCommandEnabled, unsafeCommand, workspaceFolders, homeDir);
71 |
72 | // Assert
73 | expect(commandBuilder.buildPhpmdCommand(options))
74 | .to.equal(`php /path/to/phpmd.phar "/path/to/file.php" xml "cleancode,codesize,controversial,design,unusedcode,naming"`);
75 | }
76 |
77 | @test("Should replace unix homedir and build phpmd command")
78 | public assertUnixHomeBuildPhpMDCommand() {
79 | // Arrange
80 | const command = "php /path/to/phpmd.phar";
81 | const unsafeCommand = "";
82 | const unsafeCommandEnabled = false;
83 | const workspaceFolders = [];
84 | const homeDir = "/home";
85 | const options = `"/path/to/file.php" xml "~/phpmd.xml"`;
86 |
87 | // Act
88 | const commandBuilder = new PhpmdCommandBuilder(command, unsafeCommandEnabled, unsafeCommand, workspaceFolders, homeDir);
89 |
90 | // Assert
91 | expect(commandBuilder.buildPhpmdCommand(options))
92 | .to.equal(`php /path/to/phpmd.phar "/path/to/file.php" xml "/home/phpmd.xml"`);
93 | }
94 |
95 | @test("Should replace windows homedir and build phpmd command")
96 | public assertWindowsHomeBuildPhpMDCommand() {
97 | // Arrange
98 | const command = "php /path/to/phpmd.phar";
99 | const unsafeCommand = "";
100 | const unsafeCommandEnabled = false;
101 | const workspaceFolders = [];
102 | const homeDir = "C:\\Users\\Test";
103 | const options = `"/path/to/file.php" xml "~\\phpmd.xml"`;
104 |
105 | // Act
106 | const commandBuilder = new PhpmdCommandBuilder(command, unsafeCommandEnabled, unsafeCommand, workspaceFolders, homeDir);
107 |
108 | // Assert
109 | expect(commandBuilder.buildPhpmdCommand(options))
110 | .to.equal(`php /path/to/phpmd.phar "/path/to/file.php" xml "C:\\Users\\Test\\phpmd.xml"`);
111 | }
112 |
113 | @test("Should replace workspace folder and build phpmd command")
114 | public assertWorkspaceFolderBuildPhpMDCommand() {
115 | // Arrange
116 | const command = "php /path/to/phpmd.phar";
117 | const unsafeCommand = "";
118 | const unsafeCommandEnabled = false;
119 | const workspaceFolders: WorkspaceFolder[] = [
120 | { name: "nomatch", uri: "file:///no/match/to/test" },
121 | { name: "test", uri: "file:///path/to/test" },
122 | ];
123 | const homeDir = "/home";
124 | const options = `"${URI.parse("/path/to/test/file.php").fsPath}" xml "$\{workspaceFolder\}/phpmd.xml"`;
125 |
126 | // Act
127 | const commandBuilder = new PhpmdCommandBuilder(command, unsafeCommandEnabled, unsafeCommand, workspaceFolders, homeDir);
128 |
129 | // Assert
130 | expect(commandBuilder.buildPhpmdCommand(options))
131 | .to.equal(`php /path/to/phpmd.phar "${URI.parse("/path/to/test/file.php").fsPath}" xml "${URI.parse("file:///path/to/test").fsPath}/phpmd.xml"`);
132 | }
133 |
134 | @test("Should default to first workspace folder if no match found and build phpmd command")
135 | public assertDefaultWorkspaceFolderBuildPhpMDCommand() {
136 | // Arrange
137 | const command = "php /path/to/phpmd.phar";
138 | const unsafeCommand = "";
139 | const unsafeCommandEnabled = false;
140 | const workspaceFolders: WorkspaceFolder[] = [
141 | { name: "nomatch", uri: "file:///no/match/to/test" },
142 | { name: "test", uri: "file:///path/to/test" },
143 | ];
144 | const homeDir = "/home";
145 | const options = `"${URI.parse("/not/path/to/test/file.php").fsPath}" xml "$\{workspaceFolder\}/phpmd.xml"`;
146 |
147 | // Act
148 | const commandBuilder = new PhpmdCommandBuilder(command, unsafeCommandEnabled, unsafeCommand, workspaceFolders, homeDir);
149 |
150 | // Assert
151 | expect(commandBuilder.buildPhpmdCommand(options))
152 | .to.equal(`php /path/to/phpmd.phar "${URI.parse("/not/path/to/test/file.php").fsPath}" xml "${URI.parse("file:///no/match/to/test").fsPath}/phpmd.xml"`);
153 | }
154 |
155 | @test("Should use unsafe command if enabled and not empty")
156 | public assertUsingUnsafeCommand() {
157 | // Arrange
158 | const command = "php /path/to/phpmd.phar";
159 | const unsafeCommand = "php /path/to/unsafe/phpmd.phar";
160 | const unsafeCommandEnabled = true;
161 | const workspaceFolders = [];
162 | const homeDir = "";
163 |
164 | // Act
165 | const commandBuilder = new PhpmdCommandBuilder(command, unsafeCommandEnabled, unsafeCommand, workspaceFolders, homeDir);
166 |
167 | // Assert
168 | expect(commandBuilder.buildPhpmdVersionCommand()).to.equal(`${unsafeCommand} --version`);
169 | }
170 |
171 | @test("Should not use unsafe command if not enabled and not empty")
172 | public assertNotUsingDisabledUnsafeCommand() {
173 | // Arrange
174 | const command = "php /path/to/phpmd.phar";
175 | const unsafeCommand = "php /path/to/unsafe/phpmd.phar";
176 | const unsafeCommandEnabled = false;
177 | const workspaceFolders = [];
178 | const homeDir = "";
179 |
180 | // Act
181 | const commandBuilder = new PhpmdCommandBuilder(command, unsafeCommandEnabled, unsafeCommand, workspaceFolders, homeDir);
182 |
183 | // Assert
184 | expect(commandBuilder.buildPhpmdVersionCommand()).to.equal(`${command} --version`);
185 | }
186 |
187 | @test("Should not use unsafe command if enabled and empty")
188 | public assertNotUsingEmptyUnsafeCommand() {
189 | // Arrange
190 | const command = "php /path/to/phpmd.phar";
191 | const unsafeCommand = "";
192 | const unsafeCommandEnabled = true;
193 | const workspaceFolders = [];
194 | const homeDir = "";
195 |
196 | // Act
197 | const commandBuilder = new PhpmdCommandBuilder(command, unsafeCommandEnabled, unsafeCommand, workspaceFolders, homeDir);
198 |
199 | // Assert
200 | expect(commandBuilder.buildPhpmdVersionCommand()).to.equal(`${command} --version`);
201 | }
202 | }
--------------------------------------------------------------------------------
/tests/service/PhpmdServiceTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import * as Process from "child_process";
3 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
4 | import * as sinon from "sinon";
5 | import * as stream from "stream";
6 | import PhpmdService from "../../server/service/PhpmdService";
7 | import PhpmdCommandBuilder from "../../server/service/PhpmdCommandBuilder";
8 |
9 | @suite("PhpMD service")
10 | class ServerTest {
11 |
12 | @test("Should run the command")
13 | public assertRun(done) {
14 | // Arrange
15 | // =======
16 | // Fake executable
17 | let executable = "testExecutable"
18 |
19 | // Fake commandBuilder
20 | let commandBuilderFake: PhpmdCommandBuilder = new PhpmdCommandBuilder(executable, false, "", [], "");
21 |
22 | // Fake options
23 | let options = "testOptions";
24 |
25 | // Set encoding spy
26 | let setEncodingSpy = sinon.spy();
27 |
28 | // On stub
29 | let onStub = sinon.stub();
30 | onStub.withArgs("data").callsArgWith(1, "Test data");
31 | onStub.withArgs("close").callsArg(1);
32 |
33 | // Fake process
34 | let process = {};
35 | process.stdout = {};
36 | process.stdout.setEncoding = setEncodingSpy;
37 | process.stdout.on = onStub;
38 |
39 | // Executor stub
40 | let executor = sinon.stub();
41 | executor.returns(process);
42 |
43 | // Act
44 | let service = new PhpmdService(commandBuilderFake);
45 | service.setExecutor(executor);
46 | service.run(options).then((result) => {
47 | // Assert
48 | expect(executor.calledOnce).to.be.true;
49 | expect(executor.calledWithExactly(executable + " " + options)).to.be.true;
50 | expect(result).to.equal("Test data");
51 | done();
52 | });
53 | }
54 |
55 | @test("Should reject the result promise on error")
56 | public assertRejectError(done) {
57 | // Arrange
58 | // =======
59 | // Fake executable
60 | let executable = "testExecutable";
61 |
62 | // Fake commandBuilder
63 | let commandBuilderFake: PhpmdCommandBuilder = new PhpmdCommandBuilder(executable, false, "", [], "");
64 |
65 | // Fake options
66 | let options = "testOptions";
67 |
68 | // Set encoding spy
69 | let setEncodingSpy = sinon.spy();
70 |
71 | // On stub
72 | let onStub = sinon.stub();
73 | onStub.withArgs("data").callsArgWith(1, "Test data");
74 | onStub.withArgs("error").callsArgWith(1, Error("Test error"));
75 |
76 | // Fake process
77 | let process = {};
78 | process.stdout = {};
79 | process.stdout.setEncoding = setEncodingSpy;
80 | process.stdout.on = onStub;
81 |
82 | // Executor stub
83 | let executor = sinon.stub();
84 | executor.returns(process);
85 |
86 | // Act
87 | let service = new PhpmdService(commandBuilderFake);
88 | service.setExecutor(executor);
89 | service.run(options).then(null, (err: Error) => {
90 | // Assert
91 | expect(err.message).to.equal("Test error");
92 | done();
93 | });
94 | }
95 |
96 | @test("Should reject the result promise on empty response")
97 | public assertRejectEmpty(done) {
98 | // Arrange
99 | // =======
100 | // Fake executable
101 | let executable = "testExecutable";
102 |
103 | // Fake commandBuilder
104 | let commandBuilderFake: PhpmdCommandBuilder = new PhpmdCommandBuilder(executable, false, "", [], "");
105 |
106 | // Fake options
107 | let options = "testOptions";
108 |
109 | // Set encoding spy
110 | let setEncodingSpy = sinon.spy();
111 |
112 | // On stub
113 | let onStub = sinon.stub();
114 | onStub.withArgs("close").callsArg(1);
115 |
116 | // Fake process
117 | let process = {};
118 | process.stdout = {};
119 | process.stdout.setEncoding = setEncodingSpy;
120 | process.stdout.on = onStub;
121 |
122 | // Executor stub
123 | let executor = sinon.stub();
124 | executor.returns(process);
125 |
126 | // Act
127 | let service = new PhpmdService(commandBuilderFake);
128 | service.setExecutor(executor);
129 | service.run(options).then(null, (err: Error) => {
130 | // Assert
131 | expect(err.message).to.equal("An error occured, no output was received after executing the phpmd command");
132 | done();
133 | });
134 | }
135 |
136 | @test("Should test the phpmd command")
137 | public assertTestCustomCommand(done) {
138 | // Arrange
139 | // =======
140 | // Fake executable
141 | let executable = "testExecutable";
142 |
143 | // Fake commandBuilder
144 | let commandBuilderFake: PhpmdCommandBuilder = new PhpmdCommandBuilder(executable, false, "", [], "");
145 |
146 | // Set encoding spy
147 | let setEncodingSpy = sinon.spy();
148 |
149 | // On stub
150 | let onStub = sinon.stub();
151 | onStub.withArgs("data").callsArgWith(1, "Test data");
152 | onStub.withArgs("close").callsArg(1);
153 |
154 | // Fake process
155 | let process = {};
156 | process.stdout = {};
157 | process.stdout.setEncoding = setEncodingSpy;
158 | process.stdout.on = onStub;
159 |
160 | // Executor stub
161 | let executor = sinon.stub();
162 | executor.returns(process);
163 |
164 | // Act
165 | let service = new PhpmdService(commandBuilderFake);
166 | service.setExecutor(executor);
167 | service.testPhpmd().then((result) => {
168 | // Assert
169 | expect(executor.calledOnce).to.be.true;
170 | expect(executor.calledWithExactly(executable + " --version")).to.be.true;
171 | expect(result).to.equal(true);
172 | done();
173 | });
174 | }
175 |
176 | @test("Should test the php command")
177 | public assertTestDefaultCommand(done) {
178 | // Arrange
179 | // =======
180 | // Fake executable
181 | let executable = "php testExecutable";
182 |
183 | // Fake commandBuilder
184 | let commandBuilderFake: PhpmdCommandBuilder = new PhpmdCommandBuilder(executable, false, "", [], "");
185 |
186 | // Set encoding spy
187 | let setEncodingSpy = sinon.spy();
188 |
189 | // On stub
190 | let onStub = sinon.stub();
191 | onStub.withArgs("data").callsArgWith(1, "Test data");
192 | onStub.withArgs("close").callsArg(1);
193 |
194 | // Fake process
195 | let process = {};
196 | process.stdout = {};
197 | process.stdout.setEncoding = setEncodingSpy;
198 | process.stdout.on = onStub;
199 |
200 | // Executor stub
201 | let executor = sinon.stub();
202 | executor.returns(process);
203 |
204 | // Act
205 | let service = new PhpmdService(commandBuilderFake);
206 | service.setExecutor(executor);
207 | service.testPhpmd().then((result) => {
208 | // Assert
209 | expect(executor.calledTwice).to.be.true;
210 | expect(executor.firstCall.calledWithExactly("php -v")).to.be.true;
211 | expect(executor.secondCall.calledWithExactly(executable + " --version")).to.be.true;
212 | expect(result).to.equal(true);
213 | done();
214 | });
215 | }
216 | }
217 |
--------------------------------------------------------------------------------
/tests/service/logger/NullLoggerTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import NullLogger from "../../../server/service/logger/NullLogger";
5 |
6 | @suite("NullLogger service")
7 | class NullLoggerTest {
8 |
9 | @test("Should provide a null implementation of ILogger")
10 | public assertImplementILogger() {
11 | // Arrange
12 | let logger = new NullLogger();
13 |
14 | // Act
15 | logger.setVerbose(true)
16 | .error("error")
17 | .info("info")
18 | .log("log")
19 | .warn("warn");
20 |
21 | // Assert
22 | expect(logger.getVerbose()).to.be.true;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tests/service/logger/RemoteConsoleLoggerTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import { RemoteConsole } from "vscode-languageserver";
5 | import RemoteConsoleLogger from "../../../server/service/logger/RemoteConsoleLogger";
6 |
7 | @suite("RemoteConsoleLogger service")
8 | class RemoteConsoleLoggerTest {
9 |
10 | @test("Should get/set verbose flag")
11 | public assertVerbose() {
12 | // Arrange
13 | // =======
14 | // Fake remote console
15 | let remoteConsole = {};
16 |
17 | // Create logger
18 | let logger = new RemoteConsoleLogger(remoteConsole);
19 |
20 | // Act
21 | logger.setVerbose(true);
22 |
23 | // Assert
24 | expect(logger.getVerbose()).to.be.true;
25 | }
26 |
27 | @test("Should log error messages to the remote console")
28 | public assertError() {
29 | // Arrange
30 | // =======
31 | // Spy error method
32 | let errorSpy = sinon.spy();
33 |
34 | // Fake remote console
35 | let remoteConsole = {};
36 | remoteConsole.error = errorSpy;
37 |
38 | // Create logger instance
39 | let logger = new RemoteConsoleLogger(remoteConsole);
40 |
41 | // Act
42 | logger.error("error"); // log regular error
43 | logger.error("verbose error", true); // log verbose error
44 |
45 | // Assert
46 | expect(errorSpy.calledOnce).to.be.true;
47 | expect(errorSpy.calledWithExactly("error")).to.be.true;
48 | }
49 |
50 | @test("Should log verbose error messages to the remote console")
51 | public assertErrorVerbose() {
52 | // Arrange
53 | // =======
54 | // Spy error method
55 | let errorSpy = sinon.spy();
56 |
57 | // Fake remote console
58 | let remoteConsole = {};
59 | remoteConsole.error = errorSpy;
60 |
61 | // Create logger instance
62 | let logger = new RemoteConsoleLogger(remoteConsole, true);
63 |
64 | // Act
65 | logger.error("error"); // log regular error
66 | logger.error("verbose error", true); // log verbose error
67 |
68 | // Assert
69 | expect(errorSpy.calledTwice).to.be.true;
70 | expect(errorSpy.calledWithExactly("error")).to.be.true;
71 | expect(errorSpy.calledWithExactly("verbose error")).to.be.true;
72 | }
73 |
74 | @test("Should log warning messages to the remote console")
75 | public assertWarn() {
76 | // Arrange
77 | // =======
78 | // Spy warn method
79 | let warnSpy = sinon.spy();
80 |
81 | // Fake remote console
82 | let remoteConsole = {};
83 | remoteConsole.warn = warnSpy;
84 |
85 | // Create logger instance
86 | let logger = new RemoteConsoleLogger(remoteConsole);
87 |
88 | // Act
89 | logger.warn("warn"); // log regular warning
90 | logger.warn("verbose warn", true); // log verbose warning
91 |
92 | // Assert
93 | expect(warnSpy.calledOnce).to.be.true;
94 | expect(warnSpy.calledWithExactly("warn")).to.be.true;
95 | }
96 |
97 | @test("Should log verbose warning messages to the remote console")
98 | public assertWarnVerbose() {
99 | // Arrange
100 | // =======
101 | // Spy warn method
102 | let warnSpy = sinon.spy();
103 |
104 | // Fake remote console
105 | let remoteConsole = {};
106 | remoteConsole.warn = warnSpy;
107 |
108 | // Create logger instance
109 | let logger = new RemoteConsoleLogger(remoteConsole, true);
110 |
111 | // Act
112 | logger.warn("warn"); // log regular warning
113 | logger.warn("verbose warn", true); // log verbose warning
114 |
115 | // Assert
116 | expect(warnSpy.calledTwice).to.be.true;
117 | expect(warnSpy.calledWithExactly("warn")).to.be.true;
118 | expect(warnSpy.calledWithExactly("verbose warn")).to.be.true;
119 | }
120 |
121 | @test("Should log info messages to the remote console")
122 | public assertInfo() {
123 | // Arrange
124 | // =======
125 | // Spy info method
126 | let infoSpy = sinon.spy();
127 |
128 | // Fake remote console
129 | let remoteConsole = {};
130 | remoteConsole.info = infoSpy;
131 |
132 | // Create logger instance
133 | let logger = new RemoteConsoleLogger(remoteConsole);
134 |
135 | // Act
136 | logger.info("info"); // log regular info message
137 | logger.info("verbose info", true); // log verbose info message
138 |
139 | // Assert
140 | expect(infoSpy.calledOnce).to.be.true;
141 | expect(infoSpy.calledWithExactly("info")).to.be.true;
142 | }
143 |
144 | @test("Should log verbose info messages to the remote console")
145 | public assertInfoVerbose() {
146 | // Arrange
147 | // =======
148 | // Spy info method
149 | let infoSpy = sinon.spy();
150 |
151 | // Fake remote console
152 | let remoteConsole = {};
153 | remoteConsole.info = infoSpy;
154 |
155 | // Create logger instance
156 | let logger = new RemoteConsoleLogger(remoteConsole, true);
157 |
158 | // Act
159 | logger.info("info"); // log regular info message
160 | logger.info("verbose info", true); // log verbose info message
161 |
162 | // Assert
163 | expect(infoSpy.calledTwice).to.be.true;
164 | expect(infoSpy.calledWithExactly("info")).to.be.true;
165 | expect(infoSpy.calledWithExactly("verbose info")).to.be.true;
166 | }
167 |
168 | @test("Should log log messages to the remote console")
169 | public assertLog() {
170 | // Arrange
171 | // =======
172 | // Spy log method
173 | let logSpy = sinon.spy();
174 |
175 | // Fake remote console
176 | let remoteConsole = {};
177 | remoteConsole.log = logSpy;
178 |
179 | // Create logger instance
180 | let logger = new RemoteConsoleLogger(remoteConsole);
181 |
182 | // Act
183 | logger.log("log"); // log regular log message
184 | logger.log("verbose log", true); // log verbose log message
185 |
186 | // Assert
187 | expect(logSpy.calledOnce).to.be.true;
188 | expect(logSpy.calledWithExactly("log")).to.be.true;
189 | }
190 |
191 | @test("Should log verbose log messages to the remote console")
192 | public assertLogVerbose() {
193 | // Arrange
194 | // =======
195 | // Spy log method
196 | let logSpy = sinon.spy();
197 |
198 | // Fake remote console
199 | let remoteConsole = {};
200 | remoteConsole.log = logSpy;
201 |
202 | // Create logger instance
203 | let logger = new RemoteConsoleLogger(remoteConsole, true);
204 |
205 | // Act
206 | logger.log("log"); // log regular log message
207 | logger.log("verbose log", true); // log verbose log message
208 |
209 | // Assert
210 | expect(logSpy.calledTwice).to.be.true;
211 | expect(logSpy.calledWithExactly("log")).to.be.true;
212 | expect(logSpy.calledWithExactly("verbose log")).to.be.true;
213 | }
214 | }
215 |
--------------------------------------------------------------------------------
/tests/service/notifier/ClientConnectionNotifierTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import { IConnection, MessageType, ShowMessageNotification } from "vscode-languageserver";
5 | import ClientConnectionNotifier from "../../../server/service/notifier/ClientConnectionNotifier";
6 |
7 | @suite("Client connection notifier")
8 | class ClientConnectionNotifierTest {
9 |
10 | @test("Should send an error notification")
11 | public assertError() {
12 | // Arrange
13 | // =======
14 | // Send notification spy
15 | let sendNotificationSpy = sinon.spy();
16 |
17 | // Fake connection
18 | let connection = {};
19 | connection.sendNotification = sendNotificationSpy;
20 |
21 | // Act
22 | let notifier = new ClientConnectionNotifier(connection);
23 | notifier.error("Test error");
24 |
25 | // Assert
26 | expect(sendNotificationSpy.calledOnce).to.be.true;
27 | expect(sendNotificationSpy.calledWithExactly(ShowMessageNotification.type, {
28 | message: "Test error",
29 | type: MessageType.Error
30 | })).to.be.true;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tests/service/notifier/NullNotifierTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import NullNotifier from "../../../server/service/notifier/NullNotifier";
5 |
6 | @suite("Null notifier")
7 | class NullNotifierTest {
8 |
9 | @test("Should not send an error notification")
10 | public assertError() {
11 | // Arrange
12 | // =======
13 | let notifier = new NullNotifier();
14 |
15 | // Act
16 | notifier.error("Test error");
17 |
18 | // Assert
19 | // Nothing to assert here, just validate that the method can run without problems
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tests/service/pipeline/BuildDiagnosticsStrategyTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import { Diagnostic, DiagnosticSeverity } from "vscode-languageserver";
5 | import PipelinePayloadModel from "../../../server/model/PipelinePayloadModel";
6 | import { IPmd, IPmdFileData, IPmdFileMetaData, IPmdViolation, IPmdViolationMetaData } from "../../../server/model/pmd";
7 | import BuildDiagnosticsStrategy from "../../../server/service/pipeline/BuildDiagnosticsStrategy";
8 |
9 | @suite("BuildDiagnostics strategy")
10 | class BuildDiagnosticsStrategyTest {
11 |
12 | @test("Should resolve with an array of diagnostic instances")
13 | public assertResolveDiagnostics(done) {
14 | // Arrange
15 | // =======
16 | // Fake resolve callback
17 | let resolve = (output: PipelinePayloadModel) => {
18 | // Assert
19 | // ======
20 | expect(output.diagnostics.length).to.equal(1);
21 | expect(output.diagnostics[0].message).to.equal("Test message");
22 | expect(output.diagnostics[0].range.start.line).to.equal(0);
23 | expect(output.diagnostics[0].range.start.character).to.equal(0);
24 | expect(output.diagnostics[0].range.end.line).to.equal(0);
25 | expect(output.diagnostics[0].range.end.character).to.equal(Number.MAX_VALUE);
26 | expect(output.diagnostics[0].severity).to.equal(DiagnosticSeverity.Error);
27 | expect(output.diagnostics[0].source).to.equal("PHP Mess Detector");
28 | done();
29 | };
30 |
31 | // Fake reject callback
32 | let reject = (reason: any) => {
33 | // Nothing to do here, test will fail because of absence of done
34 | };
35 |
36 | // Fake input
37 | let input = {};
38 | input.pmd = {};
39 | input.pmd.file = [];
40 | input.pmd.file[0] = {};
41 | input.pmd.file[0].$ = {};
42 | input.pmd.file[0].$.name = "Test.php";
43 | input.pmd.file[0].violation = [];
44 | input.pmd.file[0].violation[0] = {};
45 | input.pmd.file[0].violation[0]._ = "Test message";
46 | input.pmd.file[0].violation[0].$ = {};
47 | input.pmd.file[0].violation[0].$.beginline = "1";
48 | input.pmd.file[0].violation[0].$.class = "TestClass";
49 | input.pmd.file[0].violation[0].$.endline = "11";
50 | input.pmd.file[0].violation[0].$.externalInfoUrl = "http://test.com/test";
51 | input.pmd.file[0].violation[0].$.package = "TestPackage";
52 | input.pmd.file[0].violation[0].$.priority = "1";
53 | input.pmd.file[0].violation[0].$.rule = "TestRule";
54 | input.pmd.file[0].violation[0].$.ruleset = "TestRuleSet";
55 | input.diagnostics = [];
56 |
57 | // Create and configure strategy instance
58 | let strategy = new BuildDiagnosticsStrategy();
59 |
60 | // Act
61 | strategy.execute(input, resolve, reject);
62 | }
63 |
64 | @test("Should resolve without diagnostics if pmd empty")
65 | public assertResolveWithoutPmd(done) {
66 | // Arrange
67 | // =======
68 | // Fake resolve callback
69 | let resolve = (output: PipelinePayloadModel) => {
70 | // Assert
71 | // ======
72 | expect(output.diagnostics.length).to.equal(0);
73 | done();
74 | };
75 |
76 | // Fake reject callback
77 | let reject = (reason: any) => {
78 | // Nothing to do here, test will fail because of absence of done
79 | };
80 |
81 | // Fake input
82 | let input = {};
83 | input.pmd = null;
84 | input.diagnostics = [];
85 |
86 | // Create and configure factory instance
87 | let strategy = new BuildDiagnosticsStrategy();
88 |
89 | // Act
90 | strategy.execute(input, resolve, reject);
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/tests/service/pipeline/ExecuteProcessStrategyTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import * as Process from "child_process";
3 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
4 | import * as sinon from "sinon";
5 | import PipelineErrorModel from "../../../server/model/PipelineErrorModel";
6 | import PipelinePayloadModel from "../../../server/model/PipelinePayloadModel";
7 | import PhpmdService from "../../../server/service/PhpmdService";
8 | import ExecuteProcessStrategy from "../../../server/service/pipeline/ExecuteProcessStrategy";
9 | import { URI } from "vscode-uri";
10 | import PhpmdCommandBuilder from "../../../server/service/PhpmdCommandBuilder";
11 |
12 | @suite("ExectuteProcess strategy")
13 | class ExecuteProcessStrategyTest {
14 |
15 | @test("Should resolve with an array of diagnostic instances")
16 | public assertResolveExecute(done) {
17 | // Arrange
18 | // =======
19 | // Fake resolve callback
20 | let resolve = (output: PipelinePayloadModel) => {
21 | // Assert
22 | // ======
23 | expect(runStub.calledWithExactly(`"${URI.parse("testUri").fsPath}" xml "testRules"`)).to.be.true;
24 | expect(runStub.calledOnce).to.be.true;
25 | expect(output.raw).to.equal("Test data");
26 | done();
27 | };
28 |
29 | // Fake reject callback
30 | let reject = (reason: any) => {
31 | // Nothing to do here, test will fail because of absence of done
32 | };
33 |
34 | // Fake executable
35 | let executable = "testExecutable";
36 |
37 | // Fake commandBuilder
38 | let commandBuilderFake: PhpmdCommandBuilder = new PhpmdCommandBuilder(executable, false, "", [], "");
39 |
40 | // Fake rules
41 | let rules = "testRules";
42 |
43 | // Fake input
44 | let input = new PipelinePayloadModel("testUri");
45 |
46 | // Run stub
47 | let runStub = sinon.stub();
48 | runStub.returns(Promise.resolve("Test data"));
49 |
50 | // Fake service
51 | let service = {};
52 | service.run = runStub;
53 |
54 | // Initialise strategy and configure
55 | let strategy = new ExecuteProcessStrategy(commandBuilderFake, rules);
56 | strategy.setService(service);
57 |
58 | // Act
59 | strategy.execute(input, resolve, reject);
60 | }
61 |
62 | @test("Should reject with a PipelineErrorModel instance")
63 | public assertRejectExecute(done) {
64 | // Arrange
65 | // =======
66 | // Fake resolve callback
67 | let resolve = (output: PipelinePayloadModel) => {
68 | // Nothing to do here, test will fail because of absence of done
69 | };
70 |
71 | // Fake reject callback
72 | let reject = (reason: PipelineErrorModel) => {
73 | // Assert
74 | // ======
75 | expect(runStub.calledWithExactly(`"${URI.parse("testUri").fsPath}" xml "testRules"`)).to.be.true;
76 | expect(reason.error.message).to.equal("Test error");
77 | expect(reason.silent).to.be.false;
78 | done();
79 | };
80 |
81 | // Fake executable
82 | let executable = "testExecutable";
83 |
84 | // Fake commandBuilder
85 | let commandBuilderFake: PhpmdCommandBuilder = new PhpmdCommandBuilder(executable, false, "", [], "");
86 |
87 | // Fake rules
88 | let rules = "testRules";
89 |
90 | // Fake input
91 | let input = new PipelinePayloadModel("testUri");
92 |
93 | // Run stub
94 | let runStub = sinon.stub();
95 | runStub.returns(Promise.reject(Error("Test error")));
96 |
97 | // Fake service
98 | let service = {};
99 | service.run = runStub;
100 |
101 | // Initialise strategy and configure
102 | let strategy = new ExecuteProcessStrategy(commandBuilderFake, rules);
103 | strategy.setService(service);
104 |
105 | // Act
106 | strategy.execute(input, resolve, reject);
107 | }
108 |
109 | @test("Should left trim the xml result")
110 | public assertLtrimXML(done) {
111 | // Arrange
112 | // =======
113 | // Fake resolve callback
114 | let resolve = (output: PipelinePayloadModel) => {
115 | // Assert
116 | // ======
117 | expect(output.raw).to.equal("Test data");
118 | done();
119 | };
120 |
121 | // Fake reject callback
122 | let reject = (reason: any) => {
123 | // Nothing to do here, test will fail because of absence of done
124 | };
125 |
126 | // Fake executable
127 | let executable = "testExecutable";
128 |
129 | // Fake commandBuilder
130 | let commandBuilderFake: PhpmdCommandBuilder = new PhpmdCommandBuilder(executable, false, "", [], "");
131 |
132 | // Fake rules
133 | let rules = "testRules";
134 |
135 | // Fake input
136 | let input = new PipelinePayloadModel("testUri");
137 |
138 | // Run stub
139 | let runStub = sinon.stub();
140 | runStub.returns(Promise.resolve("Test ltrimXML Test data"));
141 |
142 | // Fake service
143 | let service = {};
144 | service.run = runStub;
145 |
146 | // Initialise strategy and configure
147 | let strategy = new ExecuteProcessStrategy(commandBuilderFake, rules);
148 | strategy.setService(service);
149 |
150 | // Act
151 | strategy.execute(input, resolve, reject);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/tests/service/pipeline/ParseStrategyTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import * as Xml2Js from "xml2js";
5 | import PipelinePayloadModel from "../../../server/model/PipelinePayloadModel";
6 | import ParseStrategy from "../../../server/service/pipeline/ParseStrategy";
7 | import NullLogger from "../../../server/service/logger/NullLogger";
8 | import { match } from "sinon";
9 |
10 | @suite("Parse strategy")
11 | class ParseStrategyTest {
12 |
13 | @test("Should resolve with a parsed XML")
14 | public assertResolveParse(done) {
15 | // Arrange
16 | // =======
17 | // Fake resolve callback
18 | let resolve = (output: PipelinePayloadModel | PromiseLike) => {
19 | // Assert
20 | // ======
21 | expect(input.pmd.file.length).to.equal(1);
22 | expect(input.pmd.file[0].violation.length).to.equal(1);
23 | done();
24 | };
25 |
26 | // Fake reject callback
27 | let reject = (reason: any) => {
28 | // Nothing to do here, test will fail because of absence of done
29 | };
30 |
31 | // Fake input
32 | let input = { path: "/Test.php" };
33 | input.raw = "";
34 | input.raw += "";
35 | input.raw += "";
36 | input.raw += "";
37 | input.raw += "The method activityImageAction() contains an exit expression.";
38 | input.raw += "";
39 | input.raw += "";
40 | input.raw += "";
41 |
42 | // Create and configure strategy instance
43 | let strategy = new ParseStrategy(new Xml2Js.Parser(), new NullLogger());
44 |
45 | // Act
46 | strategy.execute(input, resolve, reject);
47 | }
48 |
49 | @test("Should log XML parse errors and set pmd to null")
50 | public assertLogError(done) {
51 | let resolve = (output: PipelinePayloadModel | PromiseLike) => {
52 | // Assert
53 | // ======
54 | expect(input.pmd).to.equal(null);
55 | expect(loggerSpy.calledOnce).to.be.true;
56 | expect(loggerSpy.calledOnceWith(match(/^Unable to parse result xml./g))).to.be.true;
57 | done();
58 | };
59 |
60 | // Fake reject callback
61 | let reject = (reason: any) => {
62 | // Nothing to do here, test will fail because of absence of done
63 | };
64 |
65 | // Spy logger
66 | const logger = new NullLogger();
67 | const loggerSpy = sinon.spy(logger, "log");
68 |
69 | // Fake input
70 | let input = { path: "/Test.php" };
71 | input.raw = "";
72 | input.raw += "";
73 | input.raw += "";
74 | input.raw += "";
75 | input.raw += "The method activityImageAction() contains an exit expression.";
76 | input.raw += "";
77 | input.raw += "";
79 |
80 | // Create and configure strategy instance
81 | let strategy = new ParseStrategy(new Xml2Js.Parser(), logger);
82 |
83 | // Act
84 | strategy.execute(input, resolve, reject);
85 | }
86 |
87 | @test("Should escape XML special chars in path")
88 | public assertEscapePath(done) {
89 | // Arrange
90 | // =======
91 | // Fake resolve callback
92 | let resolve = (output: PipelinePayloadModel | PromiseLike) => {
93 | // Assert
94 | // ======
95 | expect(input.pmd.file.length).to.equal(1);
96 | expect(input.pmd.file[0].violation.length).to.equal(1);
97 | expect(input.pmd.file[0].$.name).to.equal(input.path);
98 | expect(loggerSpy.notCalled).to.be.true;
99 | done();
100 | };
101 |
102 | // Fake reject callback
103 | let reject = (reason: any) => {
104 | // Nothing to do here, test will fail because of absence of done
105 | };
106 |
107 | // Spy logger
108 | const logger = new NullLogger();
109 | const loggerSpy = sinon.spy(logger, "log");
110 |
111 | // Fake input
112 | let input = { path: "/Test&test.php" };
113 | input.raw = "";
114 | input.raw += "";
115 | input.raw += "";
116 | input.raw += "";
117 | input.raw += "The method activityImageAction() contains an exit expression.";
118 | input.raw += "";
119 | input.raw += "";
120 | input.raw += "";
121 |
122 | // Create and configure strategy instance
123 | let strategy = new ParseStrategy(new Xml2Js.Parser(), logger);
124 |
125 | // Act
126 | strategy.execute(input, resolve, reject);
127 | }
128 |
129 | @test("Should trim windows drive when escaping XML special chars")
130 | public assertTrimPath(done) {
131 | // Arrange
132 | // =======
133 | // Fake resolve callback
134 | let resolve = (output: PipelinePayloadModel | PromiseLike) => {
135 | // Assert
136 | // ======
137 | expect(input.pmd.file.length).to.equal(1);
138 | expect(input.pmd.file[0].violation.length).to.equal(1);
139 | expect(input.pmd.file[0].$.name).to.equal(input.path);
140 | expect(loggerSpy.notCalled).to.be.true;
141 | done();
142 | };
143 |
144 | // Fake reject callback
145 | let reject = (reason: any) => {
146 | // Nothing to do here, test will fail because of absence of done
147 | };
148 |
149 | // Spy logger
150 | const logger = new NullLogger();
151 | const loggerSpy = sinon.spy(logger, "log");
152 |
153 | // Fake input
154 | let input = { path: "c:\\Test&test.php" };
155 | input.raw = "";
156 | input.raw += "";
157 | input.raw += "";
158 | input.raw += "";
159 | input.raw += "The method activityImageAction() contains an exit expression.";
160 | input.raw += "";
161 | input.raw += "";
162 | input.raw += "";
163 |
164 | // Create and configure strategy instance
165 | let strategy = new ParseStrategy(new Xml2Js.Parser(), logger);
166 |
167 | // Act
168 | strategy.execute(input, resolve, reject);
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/tests/service/pipeline/TestFileStrategyTest.ts:
--------------------------------------------------------------------------------
1 | import { assert, expect } from "chai";
2 | import { only, skip, slow, suite, test, timeout } from "@testdeck/mocha";
3 | import * as sinon from "sinon";
4 | import PipelineErrorModel from "../../../server/model/PipelineErrorModel";
5 | import PipelinePayloadModel from "../../../server/model/PipelinePayloadModel";
6 | import TestFileStrategy from "../../../server/service/pipeline/TestFileStrategy";
7 |
8 | @suite("Test file strategy")
9 | class TestFileStrategyTest {
10 |
11 | @test("Should resolve if the file is readable")
12 | public assertResolveTest(done) {
13 | // Arrange
14 | // =======
15 | // Fake resolve callback
16 | let resolve = (output: PipelinePayloadModel) => {
17 | // Assert
18 | // ======
19 | expect(output).to.equal(input);
20 | done();
21 | };
22 |
23 | // Fake reject callback
24 | let reject = (reason: any) => {
25 | // Nothing to do here, test will fail because of absence of done
26 | };
27 |
28 | // Fake input
29 | let input = {};
30 | input.uri = "test.php";
31 |
32 | // Readfile stub
33 | let readfile = sinon.stub();
34 | readfile.callsArgWith(2, null, input);
35 |
36 | // Create and configure strategy instance
37 | let strategy = new TestFileStrategy(readfile);
38 |
39 | // Act
40 | strategy.execute(input, resolve, reject);
41 | }
42 |
43 | @test("Should reject if the file is not readable")
44 | public assertRejectTest(done) {
45 | // Arrange
46 | // =======
47 | // Fake resolve callback
48 | let resolve = (output: PipelinePayloadModel) => {
49 | // Nothing to do here, test will fail because of absence of done
50 | };
51 |
52 | // Fake reject callback
53 | let reject = (reason: PipelineErrorModel) => {
54 | // Assert
55 | // ======
56 | expect(reason.error.message).to.equal("Test error");
57 | expect(reason.silent).to.be.true;
58 | done();
59 | };
60 |
61 | // Fake input
62 | let input = {};
63 | input.uri = "test.php";
64 |
65 | // Readfile stub
66 | let readfile = sinon.stub();
67 | readfile.callsArgWith(2, Error("Test error"), input);
68 |
69 | // Create and configure strategy instance
70 | let strategy = new TestFileStrategy(readfile);
71 |
72 | // Act
73 | strategy.execute(input, resolve, reject);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "module": "commonjs",
5 | "moduleResolution": "node",
6 | "sourceMap": true,
7 | "lib": [ "es2016" ],
8 | "outDir": "./out",
9 | "experimentalDecorators": true
10 | },
11 | "include": [
12 | "**/*.ts"
13 | ],
14 | "exclude": [
15 | "node_modules",
16 | "out",
17 | "tests"
18 | ]
19 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tslint:recommended",
3 | "rules": {
4 | "trailing-comma": [
5 | false
6 | ]
7 | }
8 | }
--------------------------------------------------------------------------------