ember-introjs
Version:
An Ember Component for intro.js
243 lines (193 loc) • 7.84 kB
JavaScript
'use strict';
const Filter = require('broccoli-persistent-filter');
const CLIEngine = require('eslint').CLIEngine;
const defaultsDeep = require('lodash.defaultsdeep');
const md5Hex = require('md5-hex');
const stringify = require('json-stable-stringify');
const path = require('path');
const concat = require('broccoli-concat');
const isString = require('./is-string');
const getResultSeverity = require('./get-result-severity');
const testGenerators = require('./test-generators');
const testGeneratorNames = Object.keys(testGenerators);
const FACTORY_METHOD_USED = Symbol('create() factory method was used');
function resolveInputDirectory(inputNode) {
if (typeof inputNode === 'string') {
return inputNode;
}
// eslint-disable-next-line no-underscore-dangle
const nodeInfo = inputNode.__broccoliGetInfo__();
if (nodeInfo.nodeType === 'source') {
return nodeInfo.sourceDirectory;
}
if (nodeInfo.inputNodes.length > 1) {
// eslint-disable-next-line max-len
throw new Error('EslintValidationFilter can only handle one:* broccoli nodes, but part of the given input pipeline is a many:* node. (broccoli-merge-trees is an example of a many:* node) Please perform many:* operations after linting.');
}
return resolveInputDirectory(nodeInfo.inputNodes[0]);
}
/**
* Uses the content of each file in a given node and runs eslint validation on it.
* @param {Object} inputNode Node from broccoli.makeTree
* @param {{format: String, options: EslintOptions}} options Filter options
* @returns {EslintValidationFilter} Filter obconfig @constructor
*/
function EslintValidationFilter(inputNode, options) {
if (!(this instanceof EslintValidationFilter)) {
return new EslintValidationFilter(inputNode, options);
}
let _console = options.console || console;
if (!options[FACTORY_METHOD_USED]) {
_console.log('[broccoli-lint-eslint] DEPRECATION: Please use the create() factory method instead of ' +
'calling ESLint() directly or using new ESLint()');
}
options = defaultsDeep({}, options, {
persist: true,
extensions: ['js'],
options: {
ignore: true,
},
});
this.extensions = options.extensions;
// call base class constructor
Filter.call(this, inputNode, options);
this.internalOptions = options;
// set formatter
if (typeof this.internalOptions.format === 'function') {
this.formatter = this.internalOptions.format;
} else {
// eslint-disable-next-line global-require
this.formatter = require(this.internalOptions.format || 'eslint/lib/formatters/stylish');
}
this.console = _console;
this.cli = new CLIEngine(options.options);
this.eslintrc = resolveInputDirectory(inputNode);
if (isString(this.internalOptions.testGenerator)) {
this.testGenerator = testGenerators[this.internalOptions.testGenerator];
if (this.internalOptions.group && this.testGenerator) {
this.testGenerator = this.testGenerator.testOnly;
}
if (!this.testGenerator) {
throw new Error(`Could not find '${this.internalOptions.testGenerator}' test generator.`);
}
} else {
this.testGenerator = this.internalOptions.testGenerator;
}
if (this.testGenerator) {
this.targetExtension = 'lint-test.js';
}
}
module.exports = EslintValidationFilter;
EslintValidationFilter.prototype = Object.create(Filter.prototype);
EslintValidationFilter.prototype.constructor = EslintValidationFilter;
EslintValidationFilter.prototype.targetExtension = 'js';
EslintValidationFilter.prototype.baseDir = function baseDir() {
return path.join(__dirname, '..');
};
EslintValidationFilter.prototype.cacheKeyProcessString = function cacheKeyProcessString(content, relativePath) {
function functionStringifier(key, value) {
if (typeof value === 'function') {
return value.toString();
}
return value;
}
const filePath = path.join(this.eslintrc, relativePath);
const isIgnoredFile = this.cli.isPathIgnored(filePath);
return md5Hex([
content,
relativePath,
isIgnoredFile.toString(),
stringify(this.internalOptions, { replacer: functionStringifier }),
stringify(this.cli.getConfigForFile(filePath))
]);
};
EslintValidationFilter.prototype.getDestFilePath = function getDestFilePath(relativePath) {
//This method gets called for all files in the tree, so we call the superclass to filter
//based on the extensions property.
const filterPath = Filter.prototype.getDestFilePath.apply(this, arguments);
const fullPath = path.join(this.eslintrc, relativePath);
if (filterPath && !this.cli.isPathIgnored(fullPath)) {
return filterPath;
} else {
return null;
}
};
EslintValidationFilter.prototype.processString = function processString(content, relativePath) {
// verify file content
const configPath = path.join(this.eslintrc, relativePath);
const report = this.cli.executeOnText(content, configPath);
const toCache = { report, output: content };
if (this.testGenerator && Array.isArray(report.results)) {
const result = report.results[0] || {};
const messages = result.messages || [];
toCache.output = this.testGenerator(relativePath, messages, result);
}
return toCache;
};
/**
* Post-process the filtered output, calculating the result severity from the report
* if the option to `throwOnError` has been set
*
* @param {Object} results A results object returned from `processString`
* @param {Object} results.report The report returned from this.cli.executeOnText()
* @param {string} results.output The original file content passed to `processString` -- or the
* result of executing the a provided `testGenerator` function on the `report`
*
* @returns {Object} An object with an `.output` property, which will be
* used as the emitted file contents
*/
EslintValidationFilter.prototype.postProcess = function postProcess(results /* , relativePath */) {
let report = results.report;
// if verification has result
if (report.results.length &&
report.results[0].messages.length) {
// log formatter output
this.console.log(this.formatter(report.results));
const throwOnWarn = !!this.internalOptions.throwOnWarn;
const throwOnError = !!this.internalOptions.throwOnError;
if (throwOnWarn || throwOnError) {
const resultSeverity = getResultSeverity(report.results[0].messages);
if (resultSeverity === 1 && throwOnWarn) {
throw new Error('rules violation with `warn` severity level');
}
if (resultSeverity >= 2 && (throwOnWarn || throwOnError)) {
throw new Error('rules violation with `error` severity level');
}
}
}
return { output: results.output };
};
/**
* Factory function that allows to return something different
* instead of a single EslintValidationFilter.
*
* @experimental
*/
EslintValidationFilter.create = function(inputNode, _options) {
let options = Object.assign({}, _options, {
[FACTORY_METHOD_USED]: true
});
if (!options.group) {
return new EslintValidationFilter(inputNode, options);
}
if (typeof options.testGenerator !== 'string' || testGeneratorNames.indexOf(options.testGenerator) === -1) {
throw new Error(`The "group" options can only be used with a "testGenerator" option of: ${testGeneratorNames}`);
}
let testGenerator = testGenerators[options.testGenerator];
let header = testGenerator.header(options.group);
let footer = testGenerator.footer();
let eslint = new EslintValidationFilter(inputNode, options);
return concat(eslint, {
outputFile: `/${options.group}.lint-test.js`,
header,
inputFiles: ['**/*.lint-test.js'],
footer,
sourceMapConfig: { enabled: false },
allowNone: true,
});
};
Object.defineProperty(EslintValidationFilter, 'testGenerators', {
get() {
return testGeneratorNames.slice(0);
}
});