Skip to content

Commit ed546c5

Browse files
filipesperandiodblandin
authored andcommitted
Do not raise error on unsupported plugins (#204)
* Fix #192 * Decouple docs from eslint patch * Prevent error on not supported modules (plugins/extensions) Given an .eslintrc.yml: ```yaml env: es6: true node: true parserOptions: sourceType: module plugins: - node - not_supported extends: - not_valid - 'plugin:invalidplugin/recommended' - 'eslint:recommended' - 'plugin:node/recommended' rules: invalidplugin/rule: 1 node/exports-style: [error, module.exports] indent: [error, 4] linebreak-style: [error, unix] quotes: [error, double] semi: [error, always] no-console: off ``` When `CODECLIMATE_DEBUG=true codeclimate analyze` runs Then the output is like: ``` ... Module not supported: eslint-plugin-not_supported Module not supported: eslint-plugin-invalidplugin Module not supported: eslint-config-not_valid ... Analyzing: app1.js ... == app1.js (1 issue) == 1: Definition for rule 'invalidplugin/rule' was not found [eslint] ```
1 parent d1524f9 commit ed546c5

File tree

6 files changed

+155
-56
lines changed

6 files changed

+155
-56
lines changed

bin/eslint.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ process.chdir(CODE_DIR);
88
var stdout = console.log;
99
console.log = console.error;
1010

11-
var eslint = require('../lib/eslint-patch')(require('eslint'));
11+
12+
var eslint = require('../lib/eslint-patch')();
1213

1314
var CLIEngine = eslint.CLIEngine;
14-
var docs = eslint.docs;
15+
var docs = require('../lib/docs')();
1516
var fs = require("fs");
1617
var glob = require("glob");
1718
var options = { extensions: [".js"], ignore: true, reset: false, useEslintrc: true };

lib/docs.js

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
var fs = require('fs')
88
, path = require('path');
99

10-
//------------------------------------------------------------------------------
11-
// Privates
12-
//------------------------------------------------------------------------------
10+
function Docs() {
1311

14-
var docs = Object.create(null);
12+
var docs = Object.create(null);
13+
14+
function get(ruleId) {
15+
return docs[ruleId];
16+
}
1517

16-
function load() {
1718
var docsDir = path.join(__dirname, '/docs/rules');
1819

1920
fs.readdirSync(docsDir).forEach(function(file) {
@@ -22,19 +23,10 @@ function load() {
2223
// Remove the .md extension from the filename
2324
docs[file.slice(0, -3)] = content;
2425
});
25-
}
26-
27-
//------------------------------------------------------------------------------
28-
// Public Interface
29-
//------------------------------------------------------------------------------
3026

31-
exports.get = function(ruleId) {
32-
return docs[ruleId];
33-
};
34-
35-
//------------------------------------------------------------------------------
36-
// Initialization
37-
//------------------------------------------------------------------------------
27+
return {
28+
get: get
29+
};
30+
}
3831

39-
// loads existing docs
40-
load();
32+
module.exports = Docs;

lib/empty-plugin.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
rules: {},
3+
configs: {
4+
recommended: {}
5+
}
6+
};

lib/eslint-patch.js

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,50 @@
1-
'use strict';
2-
var meld = require('meld');
3-
var docs = require('./docs');
4-
var Config = require("eslint/lib/config");
5-
var ConfigUpgrader = require('./config_upgrader');
6-
7-
var supportedPlugins = ['react', 'babel'];
8-
9-
module.exports = function patcher(eslint) {
10-
11-
meld.around(eslint.CLIEngine, 'loadPlugins', function(joinPoint) {
12-
var pluginNames = joinPoint.args[0];
13-
var filteredPluginNames = pluginNames.filter(function(pluginName) {
14-
return supportedPlugins.indexOf(pluginName) >= 0;
15-
});
16-
return joinPoint.proceed(filteredPluginNames);
17-
});
18-
19-
meld.around(eslint.CLIEngine, 'addPlugin', function() {
20-
return;
21-
});
22-
23-
// meld.around(eslint.CLIEngine.Config, 'loadPackage', function(joinPoint) {
24-
// var filePath = joinPoint.args[0];
25-
// if (filePath.match(/^eslint-config-airbnb.*/)) {
26-
// return joinPoint.proceed();
27-
// } else {
28-
// return {};
29-
// }
30-
// });
1+
"use strict";
2+
3+
const Plugins = require("eslint/lib/config/plugins");
4+
const ModuleResolver = require("eslint/lib/util/module-resolver");
5+
6+
const ConfigFile = require("eslint/lib/config/config-file");
7+
8+
const Config = require("eslint/lib/config");
9+
const ConfigUpgrader = require("./config_upgrader");
10+
11+
module.exports = function patch() {
12+
13+
const skippedModules = [];
14+
function warnModuleNotSupported(name) {
15+
if(skippedModules.indexOf(name) < 0) {
16+
skippedModules.push(name);
17+
console.error(`Module not supported: ${name}`);
18+
}
19+
}
20+
21+
const resolve = ModuleResolver.prototype.resolve;
22+
ModuleResolver.prototype.resolve = function(name, path) {
23+
try {
24+
return resolve.apply(this, [name, path]);
25+
} catch(e) {
26+
warnModuleNotSupported(name);
27+
return `${__dirname}/empty-plugin.js`;
28+
}
29+
};
30+
31+
Plugins.loadAll = function(pluginNames) {
32+
const loadedPlugins = Object.keys(Plugins.getAll());
33+
if(loadedPlugins.length > 0) {
34+
return;
35+
}
36+
37+
for(const index in pluginNames) {
38+
const name = pluginNames[index];
39+
try {
40+
41+
Plugins.load(name);
42+
43+
} catch(e) {
44+
warnModuleNotSupported(`eslint-plugin-${name}`);
45+
}
46+
}
47+
};
3148

3249
const originalGetConfig = Config.prototype.getConfig;
3350
Config.prototype.getConfig = function(filePath) {
@@ -37,7 +54,5 @@ module.exports = function patcher(eslint) {
3754
return configUpgrader.upgrade(originalConfig);
3855
};
3956

40-
eslint.docs = docs;
41-
42-
return eslint;
57+
return require('eslint');
4358
};

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,7 @@
5959
"eslint-plugin-vue": "^2.0.1",
6060
"eslint-plugin-xogroup": "^1.2.0",
6161
"glob": "^7.0.6",
62-
"prettier": "^0.22.0",
63-
"meld": "^1.3.2"
62+
"prettier": "^0.22.0"
6463
},
6564
"devDependencies": {
6665
"chai": "^3.5.0",

test/eslint-patch_test.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
const expect = require("chai").expect;
2+
const sinon = require("sinon");
3+
4+
const Plugins = require("eslint/lib/config/plugins");
5+
const ModuleResolver = require("eslint/lib/util/module-resolver");
6+
const eslintPatch = require("../lib/eslint-patch");
7+
8+
describe("eslint-patch", function() {
9+
let loadAll;
10+
11+
before(function() {
12+
loadAll = Plugins.loadAll;
13+
});
14+
15+
after(function() {
16+
Plugins.loadAll = loadAll;
17+
});
18+
19+
it("intercepts plugins", function() {
20+
eslintPatch();
21+
expect(loadAll).to.not.equal(Plugins.loadAll, "Plugins.loadAll is not patched");
22+
});
23+
24+
describe("Plugins.loadAll", function() {
25+
before(function() {
26+
eslintPatch();
27+
});
28+
29+
it("delegates each plugin to be loaded", function () {
30+
Plugins.getAll = sinon.stub().returns([]);
31+
Plugins.load = sinon.spy();
32+
33+
Plugins.loadAll([ "jasmine", "mocha" ]);
34+
35+
expect(Plugins.load.calledWith("jasmine")).to.be.true;
36+
expect(Plugins.load.calledWith("mocha")).to.be.true;
37+
});
38+
39+
it("only load plugins once", function () {
40+
Plugins.getAll = sinon.stub().returns([ "node" ]);
41+
Plugins.load = sinon.spy();
42+
43+
Plugins.loadAll([ "node" ]);
44+
45+
expect(Plugins.load.called).to.be.false;
46+
});
47+
48+
it("does not raise exception for unsupported plugins", function() {
49+
Plugins.getAll = sinon.stub().returns([]);
50+
Plugins.load = sinon.stub().throws();
51+
52+
function loadPlugin() {
53+
Plugins.loadAll([ "unsupported-plugin" ]);
54+
}
55+
56+
expect(loadPlugin).to.not.throw();
57+
});
58+
});
59+
60+
describe("loading extends configuration", function() {
61+
it("patches module resolver", function() {
62+
const resolve = ModuleResolver.prototype.resolve;
63+
64+
eslintPatch();
65+
expect(ModuleResolver.prototype.resolve).to.not.eql(resolve);
66+
});
67+
68+
it("returns fake config for skipped modules", function() {
69+
eslintPatch();
70+
Plugins.loadAll(['invalidplugin']);
71+
expect(new ModuleResolver().resolve('eslint-plugin-invalidplugin')).to.match(/.+empty-plugin.js/);
72+
});
73+
74+
it("does not warn user repeatedly about not supported modules", function() {
75+
console.error = sinon.spy();
76+
eslintPatch();
77+
78+
for(var i=0; i<3; i++) {
79+
new ModuleResolver().resolve('eslint-plugin-bogus');
80+
}
81+
82+
expect(console.error.callCount).to.eql(1);
83+
});
84+
});
85+
86+
});

0 commit comments

Comments
 (0)