Skip to content
This repository has been archived by the owner on Aug 7, 2023. It is now read-only.

Expand configuration options #115

Merged
merged 6 commits into from
Sep 19, 2017
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# EditorConfig helps developers define and maintain consistent
# coding styles between different editors and IDEs
# editorconfig.org

root = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you change this to the version used elsewhere? (this).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure no problem 👍


[*]

# Change these settings to your own preference
indent_style = space
indent_size = 2

# We recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.idea
node_modules
.github_changelog_generator
package-lock.json
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
env:
global:
- COMPOSER_DISABLE_XDEBUG_WARN="1"
- secure: "fiJ/CO0SWd3PPnNbYEtgKxw2xa9fKTOx3qNK48joGEkYFngl5o18l8GlaCZo0wzjhdZdhopts0ZYejdyqRQgxA1QKfCG/Te0ukygRMPXBC+oSTY8fyCWnueZ0fF3O0wrDQUYUTLf8nPrFKOw7bOi0sn+Lcu3mUaWlGxrsHsg9GU="
- COMPOSER_OAUTH_TOKEN="cc4a091c096e7d3cfe053c3f669fb840be60ab98"

matrix:
include:
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ If you would like to contribute enhancements or fixes, please do the following:
Please note that modifications should follow these coding guidelines:

- Indent is 2 spaces.
- Code should pass [CoffeeLint](http://www.coffeelint.org/) with the provided `coffeelint.json`.
- Code should pass [ESLint](http://eslint.org/).
- Vertical whitespace helps readability, don’t be afraid to use it.

Thank you for helping out!
159 changes: 130 additions & 29 deletions lib/main.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,70 @@
'use babel';

// eslint-disable-next-line import/no-extraneous-dependencies, import/extensions
// eslint-disable-next-line import/extensions, import/no-extraneous-dependencies
import { CompositeDisposable } from 'atom';
import Path from 'path';
import * as helpers from 'atom-linter';

// Local vars
const regex = /(.+):(\d+)\t*(.+)/g;
let helpers;
let path;

// Settings
let executablePath;
let rulesets;
function loadDeps() {
if (!helpers) {
helpers = require('atom-linter');
}
if (!path) {
path = require('path');
}
}

export default {
activate() {
require('atom-package-deps').install('linter-phpmd');
this.idleCallbacks = new Set();
let depsCallbackID;
const installLinterPhpmdDeps = () => {
this.idleCallbacks.delete(depsCallbackID);
if (!atom.inSpecMode()) {
require('atom-package-deps').install('linter-phpmd');
}
loadDeps();
};
depsCallbackID = window.requestIdleCallback(installLinterPhpmdDeps);
this.idleCallbacks.add(depsCallbackID);

this.subscriptions = new CompositeDisposable();

// Backwards compatibility
if (atom.config.get('linter-phpmd.rulesets') !== undefined) {
this.rulesOrConfigFile = atom.config.get('linter-phpmd.rulesets');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

atom.config.set('linter-phpmd.rulesOrConfigFile', atom.config.get('linter-phpmd.rulesets'));

We want to save their old setting, not grab it this one time and then lose it forever 😉.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doh, you're right 🙄 where's my head at? 😜

atom.config.unset('linter-phpmd.rulesets');
}

this.subscriptions.add(
atom.config.observe('linter-phpmd.executablePath', (value) => {
executablePath = value;
this.executablePath = value;
}),
atom.config.observe('linter-phpmd.autoExecutableSearch', (value) => {
this.autoExecutableSearch = value;
}),
atom.config.observe('linter-phpmd.disableWhenNoConfigFile', (value) => {
this.disableWhenNoConfigFile = value;
}),
atom.config.observe('linter-phpmd.rulesets', (value) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember how I said to remove that check on the executableTimeout from phpcs? We actually need the same code, but for this setting, sorry!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, working on multiple projects so I'll have to dig into that to see what you mean 😜

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, to make it backwards compatbile! 👍

rulesets = value;
atom.config.observe('linter-phpmd.rulesOrConfigFile', (value) => {
this.rulesOrConfigFile = value;
}),
atom.config.observe('linter-phpmd.autoConfigSearch', (value) => {
this.autoConfigSearch = value;
}),
atom.config.observe('linter-phpmd.minimumPriority', (value) => {
this.minimumPriority = value;
}),
atom.config.observe('linter-phpmd.strictMode', (value) => {
this.strictMode = value;
}),
);
},

deactivate() {
this.idleCallbacks.forEach(callbackID => window.cancelIdleCallback(callbackID));
this.idleCallbacks.clear();
this.subscriptions.dispose();
},

Expand All @@ -41,39 +78,102 @@ export default {
const filePath = textEditor.getPath();
const fileText = textEditor.getText();

let ruleset = rulesets;
if (/^[a-z0-9]+\.xml$/gi.test(rulesets)) {
const rulesetPath = await helpers.findAsync(filePath, rulesets);
if (rulesetPath !== null) {
ruleset = rulesetPath;
if (fileText === '' || !filePath) {
// Empty file, empty results
return [];
}

loadDeps();
const fileDir = path.dirname(filePath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll need a check that filePath isn't null, although it should be impossible, sometimes linter will call Providers with TextEditors that don't have this defined (usually on restoring a session on restarting Atom).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, this needs to be fixed in linter-phpcs as well then, as far as I can remember.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, many of the providers still need the guard added to them, for some reason it's rarely an issue for most providers (I think it's a race condition in the TextEditor initializing and maybe they are slow enough it's not an issue? 😕).


let executable = this.executablePath;

// Check if a local PHPMD executable is available
if (this.autoExecutableSearch === true) {
const phpmdNames = ['vendor/bin/phpmd'];
const projExecutable = await helpers.findCachedAsync(fileDir, phpmdNames);

if (projExecutable !== null) {
executable = projExecutable;
}
}

// Rulesets
let rulesets = this.rulesOrConfigFile.join(',');
let confFile = null;

// Check if a rulesets file exists and handle it
if (this.autoConfigSearch === true) {
confFile = await helpers.findAsync(fileDir, ['phpmd.xml', 'phpmd.xml.dist', 'phpmd.ruleset.xml']);

// Check if we should stop linting when no config file could be found
if (this.disableWhenNoConfigFile && !confFile) {
return [];
}

// Override rulessets with found config file
if (confFile) {
rulesets = confFile;
}
}

// PHPMD cli parameters
const parameters = [
filePath,
'text',
ruleset,
rulesets,
];

const projectDir = atom.project.relativizePath(filePath)[0];
const options = {
// Rule priority threshold; rules with lower priority than this will not be used
if (this.minimumPriority >= 0) {
parameters.push('--minimumpriority', this.minimumPriority);
}

// Strict mode
if (this.strictMode === true) {
parameters.push('--strict');
}

// Current working dir
let workDir = fileDir;

// Determine project path
const projectPath = atom.project.relativizePath(filePath)[0];

// Set current working dir based on config path or project path
if (confFile) {
workDir = path.dirname(confFile);
} else if (projectPath) {
workDir = projectPath;
}

// PHPMD exec options
const execOptions = {
cwd: workDir,
ignoreExitCode: true,
cwd: projectDir || Path.dirname(filePath),
timeout: 1000 * 60 * 5, // ms * s * m: 5 minutes
};

const output = await helpers.exec(executablePath, parameters, options);
// Execute PHPMD
const result = await helpers.exec(executable, parameters, execOptions);

if (result === null) {
// Our specific spawn was terminated by a newer call, tell Linter not
// to update messages
return null;
}

// Check if the file contents have changed since the lint was triggered
if (textEditor.getText() !== fileText) {
// eslint-disable-next-line no-console
console.warn('linter-phpmd:: The file was modified since the ' +
'request was sent to check it. Since any results would no longer ' +
'be valid, they are not being updated. Please save the file ' +
'again to update the results.');
// Contents have changed, tell Linter not to update results
return null;
}

// Message regex
const regex = /(.+):(\d+)\t*(.+)/g;

const messages = [];
let match = regex.exec(output);
let match = regex.exec(result);
while (match !== null) {
const line = Number.parseInt(match[2], 10) - 1;
messages.push({
Expand All @@ -83,8 +183,9 @@ export default {
text: match[3],
});

match = regex.exec(output);
match = regex.exec(result);
}

return messages;
},
};
Expand Down
61 changes: 52 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,58 @@
"configSchema": {
"executablePath": {
"type": "string",
"title": "PHPMD Executable Path",
"default": "phpmd",
"description": "Enter the path to your phpmd executable.",
"order": 1
},
"rulesets": {
"type": "string",
"title": "PHPMD Rulesets",
"default": "cleancode,codesize,controversial,design,naming,unusedcode",
"description": "Comma separated list of rulesets to use in phpmd. You can also enter the name of your ruleset file (example: `ruleset.xml`) to load that from the current file's directory (or any of the parent directories)",
"autoExecutableSearch": {
"title": "Search for executables",
"type": "boolean",
"default": true,
"description": "Automatically search for `vendor/bin/phpmd` executable. Overrides the exectuable defined above.",
"order": 2
},
"rulesOrConfigFile": {
"type": "array",
"default": [
"cleancode",
"codesize",
"controversial",
"design",
"naming",
"unusedcode"
],
"items": {
"type": "string"
},
"description": "Enter path to ruleset file or a predefined rulesets.",
"order": 3
},
"disableWhenNoConfigFile": {
"type": "boolean",
"default": false,
"description": "Disable the linter when the default ruleset file is not found.",
"order": 4
},
"autoConfigSearch": {
"title": "Search for ruleset files",
"type": "boolean",
"default": true,
"description": "Automatically search for any `phpmd.xml`, `phpmd.xml.dist` or `phpmd.ruleset.xml` file to use as ruleset. Overrides custom ruleset(s) defined above.",
"order": 5
},
"minimumPriority": {
"type": "integer",
"default": -1,
"minimum": -1,
"description": "Rule priority threshold; rules with lower priority than this will not be used.",
"order": 6
},
"strictMode": {
"type": "boolean",
"default": false,
"description": "Also report those nodes with a @SuppressWarnings annotation.",
"order": 7
}
},
"homepage": "https://github.com/AtomLinter/linter-phpmd#readme",
Expand Down Expand Up @@ -71,11 +113,12 @@
}
]
},
"env": {
"node": true
},
"globals": {
"atom": true
},
"env": {
"node": true,
"browser": true
}
}
}
8 changes: 8 additions & 0 deletions spec/files/bad-suppressed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

/**
* A
* @SuppressWarnings(PHPMD)
*/
function a() {
}
30 changes: 30 additions & 0 deletions spec/linter-phpmd-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const { lint } = require('../lib/main.js').provideLinter();

const goodPath = path.join(__dirname, 'files', 'good.php');
const badPath = path.join(__dirname, 'files', 'bad.php');
const badSuppressedPath = path.join(__dirname, 'files', 'bad-suppressed.php');
const emptyPath = path.join(__dirname, 'files', 'empty.php');

describe('The phpmd provider for Linter', () => {
Expand Down Expand Up @@ -49,4 +50,33 @@ describe('The phpmd provider for Linter', () => {

expect(messages.length).toBe(0);
});

it('finds nothing wrong with suppressed warnings', async () => {
const editor = await atom.workspace.open(badSuppressedPath);
const messages = await lint(editor);

expect(messages.length).toBe(0);
});

it('finds nothing wrong with suppressed warnings by minimum priority', async () => {
atom.config.set('linter-phpmd.minimumPriority', 0);
const editor = await atom.workspace.open(badPath);
const messages = await lint(editor);

expect(messages.length).toBe(0);
});

it('verifies the messages for bad-suppressed.php with strict mode set', async () => {
atom.config.set('linter-phpmd.strictMode', true);
const editor = await atom.workspace.open(badSuppressedPath);
const messages = await lint(editor);

expect(messages.length).toBe(1);
expect(messages[0].type).toBe('Error');
expect(messages[0].html).not.toBeDefined();
expect(messages[0].text).toBe('Avoid using short method names like ::a(). ' +
'The configured minimum method name length is 3.');
expect(messages[0].filePath).toBe(badSuppressedPath);
expect(messages[0].range).toEqual([[6, 0], [6, 14]]);
});
});