hint
Version:
The linting tool for the web
231 lines (230 loc) • 8.72 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const isCI = require("is-ci");
const ora = require("ora");
const osLocale = require("os-locale");
const utils_1 = require("@hint/utils");
const utils_fs_1 = require("@hint/utils-fs");
const utils_network_1 = require("@hint/utils-network");
const utils_debug_1 = require("@hint/utils-debug");
const utils_types_1 = require("@hint/utils-types");
const __1 = require("../");
const error_status_1 = require("../enums/error-status");
const debug = (0, utils_debug_1.debug)(__filename);
const spinner = ora({ spinner: 'line' });
const showDefaultMessage = () => {
const defaultMessage = `Using the built-in configuration.
Visit https://webhint.io/docs/user-guide/ to learn how to create your own configuration.`;
utils_1.logger.log(defaultMessage);
};
const areFiles = (targets) => {
return targets.every((target) => {
return target.protocol === 'file:';
});
};
const anyFile = (targets) => {
return targets.some((target) => {
return target.protocol === 'file:';
});
};
const getDefaultConfiguration = (targets) => {
showDefaultMessage();
const targetsAreFiles = areFiles(targets);
if (!targetsAreFiles && anyFile(targets)) {
throw new Error('You cannot mix file system with urls in the analysis');
}
const ext = targetsAreFiles ? 'development' : 'web-recommended';
const config = { extends: [ext] };
if (isCI) {
config.formatters = ['html', 'stylish'];
}
return config;
};
const askUserToUseDefaultConfiguration = async (targets) => {
const question = `A valid configuration file can't be found. Do you want to use the default configuration? To know more about the default configuration see: https://webhint.io/docs/user-guide/#default-configuration`;
const confirmation = await (0, utils_1.askQuestion)(question);
if (confirmation) {
return getDefaultConfiguration(targets);
}
return null;
};
const showMissingAndIncompatiblePackages = (resources) => {
if (resources.missing.length > 0) {
utils_1.logger.log(`The following ${resources.missing.length === 1 ? 'package is' : 'packages are'} missing:
${resources.missing.join(', ')}`);
}
if (resources.incompatible.length > 0) {
utils_1.logger.log(`The following ${resources.incompatible.length === 1 ? 'package is' : 'packages are'} incompatible:
${resources.incompatible.join(', ')}`);
}
};
const askUserToInstallDependencies = async (resources) => {
showMissingAndIncompatiblePackages(resources);
const dependencies = resources.incompatible.concat(resources.missing);
const question = `There ${dependencies.length === 1 ? 'is a package' : 'are packages'} from your .hintrc file not installed or with an incompatible version. Do you want us to try to install/update them?`;
const answer = await (0, utils_1.askQuestion)(question);
return answer;
};
const getLanguage = async (userConfig, actions) => {
if (actions && actions.language) {
debug(`Using language option provided from command line: ${actions.language}`);
return actions.language;
}
if (userConfig && userConfig.language) {
debug(`Using language option provided in user config file: ${userConfig.language}`);
return userConfig.language;
}
const osLanguage = await osLocale();
debug(`Using language option configured in the OS: ${osLanguage}`);
return osLanguage;
};
const loadUserConfig = async (actions, targets) => {
let userConfig = (0, __1.getUserConfig)(actions.config);
if (!userConfig) {
userConfig = getDefaultConfiguration(targets);
}
userConfig.language = await getLanguage(userConfig, actions);
userConfig = (0, utils_1.mergeEnvWithOptions)(userConfig);
return userConfig;
};
const askToInstallPackages = async (resources) => {
const missingPackages = resources.missing.map((name) => {
return `@hint/${name}`;
});
const incompatiblePackages = resources.incompatible.map((name) => {
return `@hint/${name}@latest`;
});
if (!(await askUserToInstallDependencies(resources) &&
await (0, utils_1.installPackages)(missingPackages) &&
await (0, utils_1.installPackages)(incompatiblePackages))) {
return false;
}
return true;
};
const getAnalyzer = async (userConfig, options, targets) => {
let webhint;
try {
webhint = (0, __1.createAnalyzer)(userConfig, options);
}
catch (e) {
const error = e;
if (error.status === error_status_1.AnalyzerErrorStatus.ConfigurationError) {
const config = await askUserToUseDefaultConfiguration(targets);
if (!config) {
throw e;
}
return getAnalyzer(config, options, targets);
}
if (error.status === error_status_1.AnalyzerErrorStatus.ResourceError) {
const installed = await askToInstallPackages(error.resources);
if (!installed) {
throw e;
}
return getAnalyzer(userConfig, options, targets);
}
if (error.status === error_status_1.AnalyzerErrorStatus.HintError) {
utils_1.logger.error(`Invalid hint configuration in .hintrc: ${error.invalidHints.join(', ')}.`);
throw e;
}
if (error.status === error_status_1.AnalyzerErrorStatus.ConnectorError) {
utils_1.logger.error(`Invalid connector configuration in .hintrc`);
throw e;
}
utils_1.logger.error(e.message, e);
throw e;
}
return webhint;
};
const actionsToOptions = (actions) => {
const options = {
formatters: actions.formatters ? actions.formatters.split(',') : undefined,
hints: actions.hints ? actions.hints.split(',') : undefined,
watch: actions.watch
};
return options;
};
exports.default = async (actions) => {
const targets = (0, utils_network_1.getAsUris)(actions._);
const useSpinner = !actions.debug && !isCI;
if (targets.length === 0) {
return false;
}
const userConfig = await loadUserConfig(actions, targets);
const createAnalyzerOptions = actionsToOptions(actions);
let webhint;
try {
webhint = await getAnalyzer(userConfig, createAnalyzerOptions, targets);
}
catch (e) {
return false;
}
const start = Date.now();
let exitCode = 0;
const endSpinner = (method) => {
if (useSpinner && spinner[method]) {
spinner[method]();
}
};
const hasIssues = (reports) => {
const threshold = userConfig.severityThreshold || utils_types_1.Severity.error;
for (const result of reports) {
if (result.severity >= threshold) {
return true;
}
}
return false;
};
const print = async (reports, target, scanTime, date) => {
await webhint.format(reports, {
config: userConfig || undefined,
date,
output: actions.output ? path.resolve((0, utils_fs_1.cwd)(), actions.output) : undefined,
resources: webhint.resources,
scanTime,
target,
version: (0, utils_1.loadHintPackage)().version
});
};
const getAnalyzeOptions = () => {
const scanStart = new Map();
const analyzerOptions = {
targetEndCallback: undefined,
targetStartCallback: undefined,
updateCallback: undefined
};
if (useSpinner) {
analyzerOptions.updateCallback = (update) => {
spinner.text = update.message;
};
}
analyzerOptions.targetStartCallback = (start) => {
if (useSpinner) {
spinner.start();
}
scanStart.set(start.url, Date.now());
};
analyzerOptions.targetEndCallback = async (end) => {
const scanEnd = Date.now();
const start = scanStart.get(end.url) || 0;
if (hasIssues(end.problems)) {
exitCode = 1;
}
endSpinner(exitCode ? 'fail' : 'succeed');
await print(end.problems, end.url, scanEnd - start, new Date(start).toISOString());
};
return analyzerOptions;
};
try {
await webhint.analyze(targets, getAnalyzeOptions());
}
catch (e) {
exitCode = 1;
endSpinner('fail');
debug(`Failed to analyze: ${targets}`);
debug(e);
utils_1.logger.error(e);
}
debug(`Total runtime: ${Date.now() - start}ms`);
return exitCode === 0;
};
;