apigeelint
Version:
Node module to lint and Apigee Edge bundle.
243 lines (209 loc) • 6.96 kB
JavaScript
/*
Copyright 2019-2020 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* global process */
const fs = require("fs"),
path = require("path"),
Bundle = require("./Bundle.js"),
pluralize = require("pluralize"),
myUtil = require("./myUtil.js"),
debug = require("debug")("apigeelint:bundleLinter"),
getcb = myUtil.curry(myUtil.diagcb, debug);
function contains(a, obj, f) {
if (!a || !a.length) {
return false;
}
f =
f ||
function(lh, rh) {
return lh === rh;
};
for (var i = 0; i < a.length; i++) {
if (f(a[i], obj)) {
if (!a[i]) {
return true;
}
return a[i];
}
}
return false;
}
function exportData(path, report) {
fs.writeFile(
path + "apigeeLint.json",
JSON.stringify(report, null, 4),
"utf8",
function(err) {
if (err) {
return console.log(err);
}
}
);
}
const getPluginPath = () => path.resolve(path.join(__dirname, "plugins"));
const listPlugins = () => fs.readdirSync(getPluginPath()).filter(resolvePlugin);
const listRuleIds = () => listPlugins().map(s => s.substring(0, 5));
const listExternalRuleIds = (externalDir) => fs.readdirSync(externalDir).filter(resolvePlugin).map(s => s.substring(0, 8));
const listFormatters = () =>
fs.readdirSync(path.join(__dirname,'third_party/formatters')).filter( s => s.endsWith('.js'));
var lint = function(config, done) {
return new Bundle(config, function(bundle, err) {
if (err) {
return (done) ? done(null, err) : console.log(err);
}
// for each builtin plugin
let normalizedPath = getPluginPath();
fs.readdirSync(normalizedPath).forEach(function(file) {
if (!config.plugins || contains(config.plugins, file)) {
try {
executePlugin(normalizedPath + "/" + file, bundle);
} catch (e) {
debug("plugin error: " + file + " " + e);
}
}
});
// for each external plugin
if( config.externalPluginsDirectory ) {
fs.readdirSync(config.externalPluginsDirectory).forEach(function(file) {
if (!config.plugins || contains(config.plugins, file)) {
try {
executePlugin(path.resolve(config.externalPluginsDirectory) + "/" + file, bundle);
} catch (e) {
debug("plugin error: " + file + " " + e);
}
}
});
}
var fmt = config.formatter || "json.js",
fmtImpl = getFormatter(fmt),
fmtReport = fmtImpl(bundle.getReport());
if (config.output) {
if (typeof config.output == "function") {
config.output(fmtReport);
}
} else {
console.log(fmtReport);
}
if (fmt !== "json.js") {
(fmt = "json.js"),
(fmtImpl = getFormatter(fmt)),
(fmtReport = JSON.parse(fmtImpl(bundle.getReport())));
} else {
fmtReport = JSON.parse(fmtReport);
}
if (config.writePath) {
exportData(config.writePath, fmtReport);
}
if (done) {
done(bundle, null);
}
// Exit code should return 1 when there are errors
if (typeof config.setExitCode == 'undefined' || config.setExitCode) {
bundle.getReport().some(function(value){
if (value.errorCount > 0) {
process.exitCode = 1;
return;
}
});
// Exit code should return 1 when more than maximum number of warnings allowed
if(config.maxWarnings >=0){
let warningCount = 0;
bundle.getReport().forEach(report => warningCount += report.warningCount);
if(warningCount > config.maxWarnings){
process.exitCode = 1;
return;
}
}
}
});
};
var getFormatter = function(format) {
// default is stylish
var formatterPath;
format = format || "stylish.js";
if (typeof format === "string") {
format = format.replace(/\\/g, "/");
// if there's a slash, then it's a file
if (format.indexOf("/") > -1) {
const cwd = this.options ? this.options.cwd : process.cwd();
formatterPath = path.resolve(cwd, format);
} else {
formatterPath = `./third_party/formatters/${format}`;
}
try {
return require(formatterPath);
} catch (ex) {
ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
throw ex;
}
} else {
return null;
}
};
const bfnName = (term) =>
(term == 'Bundle') ? 'onBundle' : pluralize('on' + term, 2);
const pluginIdRe1 = new RegExp('^[A-Z]{2}[0-9]{3}$');
const pluginIdRe2 = new RegExp('^_.+\.js$');
/* exposed for testing */
const resolvePlugin = idOrFilename => {
if (idOrFilename.endsWith(".js")) {
if (idOrFilename.indexOf('/') < 0) {
if ( ! pluginIdRe2.test(idOrFilename)) {
debug(`executePlugin file(${idOrFilename}) , prepending path...`);
return path.resolve(getPluginPath(), idOrFilename);
}
else return null;
}
else {
return idOrFilename;
}
}
else if (pluginIdRe1.test(idOrFilename)) {
let p = fs.readdirSync(path.resolve(getPluginPath())).find( p => p.startsWith(idOrFilename));
return p ? path.resolve(getPluginPath(), p) : null;
}
return null;
};
const executePlugin = function(file, bundle) {
let pluginPath = resolvePlugin(file);
if (pluginPath) {
debug(`executePlugin file(${pluginPath})`);
let plugin = require(pluginPath);
if (plugin.plugin.enabled && bundle.excluded[plugin.plugin.ruleId]!==true) {
debug(`execPlugin ${pluginPath}`);
let basename = path.basename(pluginPath).slice(0, -3),
entityTypes = ['Bundle', 'Step', 'Condition',
'ProxyEndpoint', 'TargetEndpoint',
'Resource', 'Policy', 'FaultRule',
'DefaultFaultRule'];
entityTypes.forEach( etype => {
let pfn = plugin['on' + etype];
if (pfn) {
let label =`plugin ${basename} on${etype}`;
debug(label + ' start');
bundle[bfnName(etype)](pfn, getcb(label));
}
});
}
}
};
module.exports = {
lint,
getPluginPath,
listPlugins,
listRuleIds,
listExternalRuleIds,
listFormatters,
executePlugin,
resolvePlugin, // for testing
getFormatter
};