diff options
Diffstat (limited to 'tools/eslint/lib/util')
-rw-r--r-- | tools/eslint/lib/util/comment-event-generator.js | 1 | ||||
-rw-r--r-- | tools/eslint/lib/util/estraverse.js | 55 | ||||
-rw-r--r-- | tools/eslint/lib/util/glob-util.js | 82 | ||||
-rw-r--r-- | tools/eslint/lib/util/hash.js | 37 | ||||
-rw-r--r-- | tools/eslint/lib/util/module-resolver.js | 87 | ||||
-rw-r--r-- | tools/eslint/lib/util/npm-util.js | 4 | ||||
-rw-r--r-- | tools/eslint/lib/util/path-util.js | 77 | ||||
-rw-r--r-- | tools/eslint/lib/util/source-code-fixer.js | 3 | ||||
-rw-r--r-- | tools/eslint/lib/util/source-code-util.js | 10 | ||||
-rw-r--r-- | tools/eslint/lib/util/source-code.js | 12 | ||||
-rw-r--r-- | tools/eslint/lib/util/traverser.js | 56 |
11 files changed, 335 insertions, 89 deletions
diff --git a/tools/eslint/lib/util/comment-event-generator.js b/tools/eslint/lib/util/comment-event-generator.js index fb56ee1c05..90bbfe9f2f 100644 --- a/tools/eslint/lib/util/comment-event-generator.js +++ b/tools/eslint/lib/util/comment-event-generator.js @@ -24,6 +24,7 @@ function emitComments(comments, emitter, locs, eventName) { if (comments.length > 0) { comments.forEach(function(node) { var index = locs.indexOf(node.loc); + if (index >= 0) { locs.splice(index, 1); } else { diff --git a/tools/eslint/lib/util/estraverse.js b/tools/eslint/lib/util/estraverse.js deleted file mode 100644 index d2e0d9a8f1..0000000000 --- a/tools/eslint/lib/util/estraverse.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @fileoverview Patch for estraverse - * @author Toru Nagashima - * @copyright 2015 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. - */ -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -var estraverse = require("estraverse"), - jsxKeys = require("estraverse-fb/keys"); - -//------------------------------------------------------------------------------ -// Helers -//------------------------------------------------------------------------------ - -var experimentalKeys = { - ExperimentalRestProperty: ["argument"], - ExperimentalSpreadProperty: ["argument"] -}; - -/** - * Adds a given keys to Syntax and VisitorKeys of estraverse. - * - * @param {object} keys - Key definitions to add. - * This is an object as map. - * Keys are the node type. - * Values are an array of property names to visit. - * @returns {void} - */ -function installKeys(keys) { - for (var key in keys) { - /* istanbul ignore else */ - if (keys.hasOwnProperty(key)) { - estraverse.Syntax[key] = key; - if (keys[key]) { - estraverse.VisitorKeys[key] = keys[key]; - } - } - } -} - -// Add JSX node types. -installKeys(jsxKeys); -// Add Experimental node types. -installKeys(experimentalKeys); - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = estraverse; diff --git a/tools/eslint/lib/util/glob-util.js b/tools/eslint/lib/util/glob-util.js index 68e64cba64..9a704d6308 100644 --- a/tools/eslint/lib/util/glob-util.js +++ b/tools/eslint/lib/util/glob-util.js @@ -12,9 +12,11 @@ var debug = require("debug"), fs = require("fs"), + path = require("path"), glob = require("glob"), shell = require("shelljs"), + pathUtil = require("./path-util"), IgnoredPaths = require("../ignored-paths"); debug = debug("eslint:glob-util"); @@ -33,12 +35,21 @@ debug = debug("eslint:glob-util"); * * Also makes sure all path separators are POSIX style for `glob` compatibility. * - * @param {string[]} extensions An array of accepted extensions + * @param {object} [options] An options object + * @param {string[]} [options.extensions=[".js"]] An array of accepted extensions + * @param {string} [options.cwd=process.cwd()] The cwd to use to resolve relative pathnames * @returns {Function} A function that takes a pathname and returns a glob that * matches all files with the provided extensions if * pathname is a directory. */ -function processPath(extensions) { +function processPath(options) { + var cwd = (options && options.cwd) || process.cwd(); + var extensions = (options && options.extensions) || [".js"]; + + extensions = extensions.map(function(ext) { + return ext.charAt(0) === "." ? ext.substr(1) : ext; + }); + var suffix = "/**"; if (extensions.length === 1) { @@ -56,12 +67,13 @@ function processPath(extensions) { */ return function(pathname) { var newPath = pathname; + var resolvedPath = path.resolve(cwd, pathname); - if (shell.test("-d", pathname)) { + if (shell.test("-d", resolvedPath)) { newPath = pathname.replace(/[\/\\]$/, "") + suffix; } - return newPath.replace(/\\/g, "/").replace(/^\.\//, ""); + return pathUtil.convertPathToPosix(newPath); }; } @@ -70,19 +82,15 @@ function processPath(extensions) { //------------------------------------------------------------------------------ /** - * Resolves the patterns into glob-based patterns for easier handling. + * Resolves any directory patterns into glob-based patterns for easier handling. * @param {string[]} patterns File patterns (such as passed on the command line). - * @param {string[]} extensions List of valid file extensions. - * @returns {string[]} The equivalent glob patterns. + * @param {Object} options An options object. + * @returns {string[]} The equivalent glob patterns and filepath strings. */ -function resolveFileGlobPatterns(patterns, extensions) { - extensions = extensions || [".js"]; +function resolveFileGlobPatterns(patterns, options) { - extensions = extensions.map(function(ext) { - return ext.charAt(0) === "." ? ext.substr(1) : ext; - }); + var processPathExtensions = processPath(options); - var processPathExtensions = processPath(extensions); return patterns.map(processPathExtensions); } @@ -92,6 +100,7 @@ function resolveFileGlobPatterns(patterns, extensions) { * * @param {string[]} globPatterns Glob patterns. * @param {Object} [options] An options object. + * @param {string} [options.cwd] CWD (considered for relative filenames) * @param {boolean} [options.ignore] False disables use of .eslintignore. * @param {string} [options.ignorePath] The ignore file to use instead of .eslintignore. * @param {string} [options.ignorePattern] A pattern of files to ignore. @@ -99,46 +108,63 @@ function resolveFileGlobPatterns(patterns, extensions) { */ function listFilesToProcess(globPatterns, options) { var ignoredPaths, - ignoredPathsList, files = [], added = {}, - globOptions, - rulesKey = "_rules"; + globOptions; + + var cwd = (options && options.cwd) || process.cwd(); /** * Executes the linter on a file defined by the `filename`. Skips * unsupported file extensions and any files that are already linted. * @param {string} filename The file to be processed + * @param {boolean} shouldWarnIgnored Whether or not a report should be made if + * the file is ignored * @returns {void} */ - function addFile(filename) { - if (ignoredPaths.contains(filename)) { - return; + function addFile(filename, shouldWarnIgnored) { + var ignored = false; + var isSilentlyIgnored; + + if (options.ignore !== false) { + if (ignoredPaths.contains(filename, "default")) { + isSilentlyIgnored = true; + } + if (ignoredPaths.contains(filename, "custom")) { + if (shouldWarnIgnored) { + ignored = true; + } else { + isSilentlyIgnored = true; + } + } + if (isSilentlyIgnored && !ignored) { + return; + } } - filename = fs.realpathSync(filename); if (added[filename]) { return; } - files.push(filename); + files.push({filename: filename, ignored: ignored}); added[filename] = true; } options = options || { ignore: true, dotfiles: true }; ignoredPaths = new IgnoredPaths(options); - ignoredPathsList = ignoredPaths.ig.custom[rulesKey].map(function(rule) { - return rule.pattern; - }); globOptions = { nodir: true, - ignore: ignoredPathsList + cwd: cwd }; debug("Creating list of files to process."); globPatterns.forEach(function(pattern) { - if (shell.test("-f", pattern)) { - addFile(pattern); + var file = path.resolve(cwd, pattern); + + if (shell.test("-f", file)) { + addFile(fs.realpathSync(file), !shell.test("-d", file)); } else { - glob.sync(pattern, globOptions).forEach(addFile); + glob.sync(pattern, globOptions).forEach(function(globMatch) { + addFile(path.resolve(cwd, globMatch), false); + }); } }); diff --git a/tools/eslint/lib/util/hash.js b/tools/eslint/lib/util/hash.js new file mode 100644 index 0000000000..b0271bbf03 --- /dev/null +++ b/tools/eslint/lib/util/hash.js @@ -0,0 +1,37 @@ +/** + * @fileoverview Defining the hashing function in one place. + * @author Michael Ficarra + * @copyright 2016 Michael Ficarra. All rights reserved. + * See LICENSE file in root directory for full license. + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +var murmur = require("imurmurhash"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +/** + * hash the given string + * @param {string} str the string to hash + * @returns {string} the hash + */ +function hash(str) { + return murmur(str).result().toString(36); +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = hash; diff --git a/tools/eslint/lib/util/module-resolver.js b/tools/eslint/lib/util/module-resolver.js new file mode 100644 index 0000000000..9df544cf2b --- /dev/null +++ b/tools/eslint/lib/util/module-resolver.js @@ -0,0 +1,87 @@ +/** + * @fileoverview Implements the Node.js require.resolve algorithm + * @author Nicholas C. Zakas + * @copyright 2016 Nicholas C. Zakas. All rights reserved. + * See LICENSE file in root directory for full license. + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +var lodash = require("lodash"), + Module = require("module"); + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +var DEFAULT_OPTIONS = { + + /* + * module.paths is an array of paths to search for resolving things relative + * to this file. Module.globalPaths contains all of the special Node.js + * directories that can also be searched for modules. + */ + lookupPaths: module.paths.concat(Module.globalPaths) +}; + +/** + * Resolves modules based on a set of options. + * @param {Object} options The options for resolving modules. + * @param {string[]} options.lookupPaths An array of paths to include in the + * lookup with the highest priority paths coming first. + * @constructor + */ +function ModuleResolver(options) { + options = options || {}; + + this.options = lodash.assign({}, DEFAULT_OPTIONS, options); +} + +ModuleResolver.prototype = { + + /** + * Resolves the file location of a given module relative to the configured + * lookup paths. + * @param {string} name The module name to resolve. + * @param {string} extraLookupPath An extra path to look into for the module. + * This path is used with the highest priority. + * @returns {string} The resolved file path for the module. + * @throws {Error} If the module cannot be resolved. + */ + resolve: function(name, extraLookupPath) { + + /* + * First, clone the lookup paths so we're not messing things up for + * subsequent calls to this function. Then, move the extraLookupPath to the + * top of the lookup paths list so it will be searched first. + */ + var lookupPaths = this.options.lookupPaths.concat(); + + lookupPaths.unshift(extraLookupPath); + + /** + * Module._findPath is an internal method to Node.js, then one they use to + * lookup file paths when require() is called. So, we are hooking into the + * exact same logic that Node.js uses. + */ + var result = Module._findPath(name, lookupPaths); // eslint-disable-line no-underscore-dangle + + if (!result) { + throw new Error("Cannot find module '" + name + "'"); + } + + return result; + + } + +}; + +//------------------------------------------------------------------------------ +// Public API +//------------------------------------------------------------------------------ + +module.exports = ModuleResolver; diff --git a/tools/eslint/lib/util/npm-util.js b/tools/eslint/lib/util/npm-util.js index fc9d3a6c70..c45f079011 100644 --- a/tools/eslint/lib/util/npm-util.js +++ b/tools/eslint/lib/util/npm-util.js @@ -28,8 +28,10 @@ var fs = require("fs"), */ function findPackageJson(startDir) { var dir = path.resolve(startDir || process.cwd()); + do { var pkgfile = path.join(dir, "package.json"); + if (!fs.existsSync(pkgfile)) { dir = path.join(dir, ".."); continue; @@ -69,10 +71,12 @@ function installSyncSaveDev(packages) { function check(packages, opt) { var deps = []; var pkgJson = (opt) ? findPackageJson(opt.startDir) : findPackageJson(); + if (!pkgJson) { throw new Error("Could not find a package.json file. Run 'npm init' to create one."); } var fileJson = JSON.parse(fs.readFileSync(pkgJson, "utf8")); + if (opt.devDependencies && typeof fileJson.devDependencies === "object") { deps = deps.concat(Object.keys(fileJson.devDependencies)); } diff --git a/tools/eslint/lib/util/path-util.js b/tools/eslint/lib/util/path-util.js new file mode 100644 index 0000000000..d1c8197adf --- /dev/null +++ b/tools/eslint/lib/util/path-util.js @@ -0,0 +1,77 @@ +/** + * @fileoverview Common helpers for operations on filenames and paths + * @author Ian VanSchooten + * @copyright 2016 Ian VanSchooten. All rights reserved. + * See LICENSE in root directory for full license. + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +var path = require("path"), + isAbsolute = require("path-is-absolute"); + +//------------------------------------------------------------------------------ +// Private +//------------------------------------------------------------------------------ + +/** + * Replace Windows with posix style paths + * + * @param {string} filepath Path to convert + * @returns {string} Converted filepath + */ +function convertPathToPosix(filepath) { + var normalizedFilepath = path.normalize(filepath); + var posixFilepath = normalizedFilepath.replace(/\\/g, "/"); + + return posixFilepath; +} + +/** + * Converts an absolute filepath to a relative path from a given base path + * + * For example, if the filepath is `/my/awesome/project/foo.bar`, + * and the base directory is `/my/awesome/project/`, + * then this function should return `foo.bar`. + * + * path.relative() does something similar, but it requires a baseDir (`from` argument). + * This function makes it optional and just removes a leading slash if the baseDir is not given. + * + * It does not take into account symlinks (for now). + * + * @param {string} filepath Path to convert to relative path. If already relative, + * it will be assumed to be relative to process.cwd(), + * converted to absolute, and then processed. + * @param {string} [baseDir] Absolute base directory to resolve the filepath from. + * If not provided, all this function will do is remove + * a leading slash. + * @returns {string} Relative filepath + */ +function getRelativePath(filepath, baseDir) { + var relativePath; + + if (!isAbsolute(filepath)) { + filepath = path.resolve(filepath); + } + if (baseDir) { + if (!isAbsolute(baseDir)) { + throw new Error("baseDir should be an absolute path"); + } + relativePath = path.relative(baseDir, filepath); + } else { + relativePath = filepath.replace(/^\//, ""); + } + return relativePath; +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + convertPathToPosix: convertPathToPosix, + getRelativePath: getRelativePath +}; diff --git a/tools/eslint/lib/util/source-code-fixer.js b/tools/eslint/lib/util/source-code-fixer.js index da7a1f98d0..0ea68d382b 100644 --- a/tools/eslint/lib/util/source-code-fixer.js +++ b/tools/eslint/lib/util/source-code-fixer.js @@ -105,11 +105,14 @@ SourceCodeFixer.applyFixes = function(sourceCode, messages) { if (end < lastFixPos) { if (start < 0) { + // Remove BOM. prefix = ""; start = 0; } + if (start === 0 && insertionText[0] === BOM) { + // Set BOM. prefix = BOM; insertionText = insertionText.slice(1); diff --git a/tools/eslint/lib/util/source-code-util.js b/tools/eslint/lib/util/source-code-util.js index 3a5396fdc0..b12b095f17 100644 --- a/tools/eslint/lib/util/source-code-util.js +++ b/tools/eslint/lib/util/source-code-util.js @@ -36,11 +36,14 @@ function getSourceCodeOfFile(filename, options) { var opts = lodash.assign({}, options, { rules: {}}); var cli = new CLIEngine(opts); var results = cli.executeOnFiles([filename]); + if (results && results.results[0] && results.results[0].messages[0] && results.results[0].messages[0].fatal) { var msg = results.results[0].messages[0]; + throw new Error("(" + filename + ":" + msg.line + ":" + msg.column + ") " + msg.message); } var sourceCode = eslint.getSourceCode(); + return sourceCode; } @@ -84,14 +87,17 @@ function getSourceCodeOfFiles(patterns, options, cb) { opts = lodash.assign({}, defaultOptions, options); } debug("constructed options:", opts); + patterns = globUtil.resolveFileGlobPatterns(patterns, opts); - patterns = globUtil.resolveFileGlobPatterns(patterns, opts.extensions); - filenames = globUtil.listFilesToProcess(patterns, opts); + filenames = globUtil.listFilesToProcess(patterns, opts).reduce(function(files, fileInfo) { + return !fileInfo.ignored ? files.concat(fileInfo.filename) : files; + }, []); if (filenames.length === 0) { debug("Did not find any files matching pattern(s): " + patterns); } filenames.forEach(function(filename) { var sourceCode = getSourceCodeOfFile(filename, opts); + if (sourceCode) { debug("got sourceCode of", filename); sourceCodes[filename] = sourceCode; diff --git a/tools/eslint/lib/util/source-code.js b/tools/eslint/lib/util/source-code.js index 9dd0a45120..a79bd5b531 100644 --- a/tools/eslint/lib/util/source-code.js +++ b/tools/eslint/lib/util/source-code.js @@ -12,7 +12,7 @@ var lodash = require("lodash"), createTokenStore = require("../token-store.js"), - estraverse = require("./estraverse"); + Traverser = require("./traverser"); //------------------------------------------------------------------------------ // Private @@ -126,11 +126,13 @@ function SourceCode(text, ast) { // create token store methods var tokenStore = createTokenStore(ast.tokens); + Object.keys(tokenStore).forEach(function(methodName) { this[methodName] = tokenStore[methodName]; }, this); var tokensAndCommentsStore = createTokenStore(this.tokensAndComments); + this.getTokenOrCommentBefore = tokensAndCommentsStore.getTokenBefore; this.getTokenOrCommentAfter = tokensAndCommentsStore.getTokenAfter; @@ -261,10 +263,11 @@ SourceCode.prototype = { * @returns {ASTNode} The node if found or null if not found. */ getNodeByRangeIndex: function(index) { - var result = null; - var resultParent = null; + var result = null, + resultParent = null, + traverser = new Traverser(); - estraverse.traverse(this.ast, { + traverser.traverse(this.ast, { enter: function(node, parent) { if (node.range[0] <= index && index < node.range[1]) { result = node; @@ -294,6 +297,7 @@ SourceCode.prototype = { */ isSpaceBetweenTokens: function(first, second) { var text = this.text.slice(first.range[1], second.range[0]); + return /\s/.test(text.replace(/\/\*.*?\*\//g, "")); } }; diff --git a/tools/eslint/lib/util/traverser.js b/tools/eslint/lib/util/traverser.js new file mode 100644 index 0000000000..74cfbec2b2 --- /dev/null +++ b/tools/eslint/lib/util/traverser.js @@ -0,0 +1,56 @@ +/** + * @fileoverview Wrapper around estraverse + * @author Nicholas C. Zakas + * @copyright 2016 Nicholas C. Zakas. All rights reserved. + * See LICENSE in root directory for full license. + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +var estraverse = require("estraverse"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +var KEY_BLACKLIST = [ + "parent", + "leadingComments", + "trailingComments" +]; + +/** + * Wrapper around an estraverse controller that ensures the correct keys + * are visited. + * @constructor + */ +function Traverser() { + + var controller = Object.create(new estraverse.Controller()), + originalTraverse = controller.traverse; + + // intercept call to traverse() and add the fallback key to the visitor + controller.traverse = function(node, visitor) { + visitor.fallback = Traverser.getKeys; + return originalTraverse.call(this, node, visitor); + }; + + return controller; +} + +/** + * Calculates the keys to use for traversal. + * @param {ASTNode} node The node to read keys from. + * @returns {string[]} An array of keys to visit on the node. + * @private + */ +Traverser.getKeys = function(node) { + return Object.keys(node).filter(function(key) { + return KEY_BLACKLIST.indexOf(key) === -1; + }); +}; + +module.exports = Traverser; |