"use strict"; var _ = require("lodash"); var cli = require("cli"); var path = require("path"); var minimatch = require("minimatch"); var htmlparser = require("htmlparser2"); var exit = require("exit"); var stripJsonComments = require("strip-json-comments"); var JSHINT = require("./jshint.js").JSHINT; var defReporter = require("./reporters/default").reporter; var fsUtils = require("./fs-utils"); var OPTIONS = { "config": ["c", "Custom configuration file", "string", false ], "reporter": ["reporter", "Custom reporter (|jslint|checkstyle|unix)", "string", undefined ], "prereq": [ "prereq", "Comma-separated list of prerequisites (paths). E.g. files which include " + "definitions of global variables used throughout your project", "string", null ], "exclude": ["exclude", "Exclude files matching the given filename pattern (same as .jshintignore)", "string", null], "exclude-path": ["exclude-path", "Pass in a custom jshintignore file path", "string", null], "filename": ["filename", "Pass in a filename when using STDIN to emulate config lookup for that file name", "string", null], "verbose": ["verbose", "Show message codes"], "show-non-errors": ["show-non-errors", "Show additional data generated by jshint"], "extra-ext": ["e", "Comma-separated list of file extensions to use (default is .js)", "string", ""], "extract": [ "extract", "Extract inline scripts contained in HTML (auto|always|never, default to never)", "string", "never" ], // Deprecated options. "jslint-reporter": [ "jslint-reporter", deprecated("Use a jslint compatible reporter", "--reporter=jslint") ], "checkstyle-reporter": [ "checkstyle-reporter", deprecated("Use a CheckStyle compatible XML reporter", "--reporter=checkstyle") ] }; /** * Returns the same text but with a deprecation notice. * Useful for options descriptions. * * @param {string} text * @param {string} alt (optional) Alternative command to include in the * deprecation notice. * * @returns {string} */ function deprecated(text, alt) { if (!alt) { return text + " (DEPRECATED)"; } return text + " (DEPRECATED, use " + alt + " instead)"; } /** * Tries to find a configuration file in either project directory * or in the home directory. Configuration files are named * '.jshintrc'. * * @param {string} file path to the file to be linted * @returns {string} a path to the config file */ function findConfig(file) { var dir = path.dirname(path.resolve(file)); var envs = getHomeDir(); var proj = findFile(".jshintrc", dir); var home; if (proj) return proj; else if (envs) { home = path.normalize(path.join(envs, ".jshintrc")); if (fsUtils.exists(home)) return home; } return null; } function getHomeDir() { var homePath = ""; var environment = global.process.env; var paths = [ environment.USERPROFILE, environment.HOME, environment.HOMEPATH, environment.HOMEDRIVE + environment.HOMEPATH ]; while (paths.length) { homePath = paths.shift(); if (fsUtils.exists(homePath)) { return homePath; } } } /** * Tries to find JSHint configuration within a package.json file * (if any). It search in the current directory and then goes up * all the way to the root just like findFile. * * @param {string} file path to the file to be linted * @returns {object} config object */ function loadNpmConfig(file) { var dir = path.dirname(path.resolve(file)); var fp = findFile("package.json", dir); if (!fp) return null; try { return require(fp).jshintConfig; } catch (e) { return null; } } /** * Tries to import a reporter file and returns its reference. * * @param {string} fp a path to the reporter file * @returns {object} imported module for the reporter or 'null' * if a module cannot be imported. */ function loadReporter(fp) { try { return require(fp).reporter; } catch (err) { return null; } } // Storage for memoized results from find file // Should prevent lots of directory traversal & // lookups when liniting an entire project var findFileResults = {}; /** * Searches for a file with a specified name starting with * 'dir' and going all the way up either until it finds the file * or hits the root. * * @param {string} name filename to search for (e.g. .jshintrc) * @param {string} dir directory to start search from (default: * current working directory) * * @returns {string} normalized filename */ function findFile(name, cwd) { cwd = cwd || process.cwd(); var filename = path.normalize(path.join(cwd, name)); if (findFileResults[filename] !== undefined) { return findFileResults[filename]; } var parent = path.resolve(cwd, "../"); if (fsUtils.exists(filename)) { findFileResults[filename] = filename; return filename; } if (cwd === parent) { findFileResults[filename] = null; return null; } return findFile(name, parent); } /** * Loads a list of files that have to be skipped. JSHint assumes that * the list is located in a file called '.jshintignore'. * * @return {array} a list of files to ignore. */ function loadIgnores(params) { var file = findFile(params.excludePath || ".jshintignore", params.cwd) || ""; if (!file && !params.exclude) { return []; } var lines = (file ? fsUtils.readFile(file) : "").split("\n"); var exclude = params.exclude || ""; lines.unshift.apply(lines, exclude.split(",")); return lines .filter(function(line) { return !!line.trim(); }) .map(function(line) { if (line[0] === "!") return "!" + path.resolve(path.dirname(file), line.substr(1).trim()); return path.join(path.dirname(file), line.trim()); }); } /** * Checks whether we should ignore a file or not. * * @param {string} fp a path to a file * @param {array} patterns a list of patterns for files to ignore * * @return {boolean} 'true' if file should be ignored, 'false' otherwise. */ function isIgnored(fp, patterns) { return patterns.some(function(ip) { if (minimatch(path.resolve(fp), ip, { nocase: true, dot: true })) { return true; } if (path.resolve(fp) === ip) { return true; } if (fsUtils.isDirectory(fp) && ip.match(/^[^\/\\]*[\/\\]?$/) && fp.match(new RegExp("^" + ip + ".*"))) { return true; } }); } /** * Extract JS code from a given source code. The source code my be either HTML * code or JS code. In the latter case, no extraction will be done unless * 'always' is given. * * @param {string} code a piece of code * @param {string} when 'always' will extract the JS code, no matter what. * 'never' won't do anything. 'auto' will check if the code looks like HTML * before extracting it. * * @return {string} the extracted code */ function extract(code, when) { // A JS file won't start with a less-than character, whereas a HTML file // should always start with that. if (when !== "always" && (when !== "auto" || !/^\s* tag. function onopen(name, attrs) { if (name !== "script") return; if (attrs.type && !/text\/javascript/.test(attrs.type.toLowerCase())) return; // Mark that we're inside a tag and this tag and this