diff --git a/tools/eslint/lib/config.js b/tools/eslint/lib/config.js index c69d120ef7fe26..2db87d9426c64f 100644 --- a/tools/eslint/lib/config.js +++ b/tools/eslint/lib/config.js @@ -66,13 +66,14 @@ class Config { this.parser = options.parser; this.parserOptions = options.parserOptions || {}; + this.configCache = new ConfigCache(); + this.baseConfig = options.baseConfig ? ConfigOps.merge({}, ConfigFile.loadObject(options.baseConfig, this)) : { rules: {} }; this.baseConfig.filePath = ""; this.baseConfig.baseDirectory = this.options.cwd; - this.configCache = new ConfigCache(); this.configCache.setConfig(this.baseConfig.filePath, this.baseConfig); this.configCache.setMergedVectorConfig(this.baseConfig.filePath, this.baseConfig); diff --git a/tools/eslint/lib/config/config-cache.js b/tools/eslint/lib/config/config-cache.js index 0ffcae9440ffe0..07436a87c8fc2c 100644 --- a/tools/eslint/lib/config/config-cache.js +++ b/tools/eslint/lib/config/config-cache.js @@ -24,12 +24,12 @@ function hash(vector) { //------------------------------------------------------------------------------ /** - * Configuration caching class (not exported) + * Configuration caching class */ -class ConfigCache { +module.exports = class ConfigCache { constructor() { - this.filePathCache = new Map(); + this.configFullNameCache = new Map(); this.localHierarchyCache = new Map(); this.mergedVectorCache = new Map(); this.mergedCache = new Map(); @@ -37,23 +37,25 @@ class ConfigCache { /** * Gets a config object from the cache for the specified config file path. - * @param {string} configFilePath the absolute path to the config file + * @param {string} configFullName the name of the configuration as used in the eslint config(e.g. 'plugin:node/recommended'), + * or the absolute path to a config file. This should uniquely identify a config. * @returns {Object|null} config object, if found in the cache, otherwise null * @private */ - getConfig(configFilePath) { - return this.filePathCache.get(configFilePath); + getConfig(configFullName) { + return this.configFullNameCache.get(configFullName); } /** * Sets a config object in the cache for the specified config file path. - * @param {string} configFilePath the absolute path to the config file + * @param {string} configFullName the name of the configuration as used in the eslint config(e.g. 'plugin:node/recommended'), + * or the absolute path to a config file. This should uniquely identify a config. * @param {Object} config the config object to add to the cache * @returns {void} * @private */ - setConfig(configFilePath, config) { - this.filePathCache.set(configFilePath, config); + setConfig(configFullName, config) { + this.configFullNameCache.set(configFullName, config); } /** @@ -125,6 +127,4 @@ class ConfigCache { setMergedConfig(vector, config) { this.mergedCache.set(hash(vector), config); } -} - -module.exports = ConfigCache; +}; diff --git a/tools/eslint/lib/config/config-file.js b/tools/eslint/lib/config/config-file.js index 832529952879f4..3c790cf3be2087 100644 --- a/tools/eslint/lib/config/config-file.js +++ b/tools/eslint/lib/config/config-file.js @@ -488,12 +488,15 @@ function normalizePackageName(name, prefix) { * @returns {Object} An object containing 3 properties: * - 'filePath' (required) the resolved path that can be used directly to load the configuration. * - 'configName' the name of the configuration inside the plugin. - * - 'configFullName' the name of the configuration as used in the eslint config (e.g. 'plugin:node/recommended'). + * - 'configFullName' (required) the name of the configuration as used in the eslint config(e.g. 'plugin:node/recommended'), + * or the absolute path to a config file. This should uniquely identify a config. * @private */ function resolve(filePath, relativeTo) { if (isFilePath(filePath)) { - return { filePath: path.resolve(relativeTo || "", filePath) }; + const fullPath = path.resolve(relativeTo || "", filePath); + + return { filePath: fullPath, configFullName: fullPath }; } let normalizedPackageName; @@ -510,7 +513,7 @@ function resolve(filePath, relativeTo) { normalizedPackageName = normalizePackageName(filePath, "eslint-config"); debug(`Attempting to resolve ${normalizedPackageName}`); filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo)); - return { filePath }; + return { filePath, configFullName: filePath }; } @@ -560,11 +563,12 @@ function loadFromDisk(resolvedPath, configContext) { /** * Loads a config object, applying extends if present. * @param {Object} configObject a config object to load + * @param {Config} configContext Context for the config instance * @returns {Object} the config object with extends applied if present, or the passed config if not * @private */ -function loadObject(configObject) { - return configObject.extends ? applyExtends(configObject, "") : configObject; +function loadObject(configObject, configContext) { + return configObject.extends ? applyExtends(configObject, configContext, "") : configObject; } /** @@ -579,7 +583,7 @@ function loadObject(configObject) { function load(filePath, configContext, relativeTo) { const resolvedPath = resolve(filePath, relativeTo); - const cachedConfig = configContext.configCache.getConfig(resolvedPath.filePath); + const cachedConfig = configContext.configCache.getConfig(resolvedPath.configFullName); if (cachedConfig) { return cachedConfig; @@ -590,7 +594,7 @@ function load(filePath, configContext, relativeTo) { if (config) { config.filePath = resolvedPath.filePath; config.baseDirectory = path.dirname(resolvedPath.filePath); - configContext.configCache.setConfig(resolvedPath.filePath, config); + configContext.configCache.setConfig(resolvedPath.configFullName, config); } return config; diff --git a/tools/eslint/lib/rules/indent.js b/tools/eslint/lib/rules/indent.js index 52d08ff3d597d9..3027e772043772 100644 --- a/tools/eslint/lib/rules/indent.js +++ b/tools/eslint/lib/rules/indent.js @@ -220,8 +220,8 @@ class OffsetStorage { /** * Sets the offset column of token B to match the offset column of token A. - * This is different from matchIndentOf because it matches a *column*, even if baseToken is not - * the first token on its line. + * **WARNING**: This is different from matchIndentOf because it matches a *column*, even if baseToken is not + * the first token on its line. In most cases, `matchIndentOf` should be used instead. * @param {Token} baseToken The first token * @param {Token} offsetToken The second token, whose offset should be matched to the first token * @returns {void} @@ -239,12 +239,62 @@ class OffsetStorage { } /** - * Sets the desired offset of a token - * @param {Token} token The token - * @param {Token} offsetFrom The token that this is offset from - * @param {number} offset The desired indent level - * @returns {void} - */ + * Sets the desired offset of a token. + * + * This uses a line-based offset collapsing behavior to handle tokens on the same line. + * For example, consider the following two cases: + * + * ( + * [ + * bar + * ] + * ) + * + * ([ + * bar + * ]) + * + * Based on the first case, it's clear that the `bar` token needs to have an offset of 1 indent level (4 spaces) from + * the `[` token, and the `[` token has to have an offset of 1 indent level from the `(` token. Since the `(` token is + * the first on its line (with an indent of 0 spaces), the `bar` token needs to be offset by 2 indent levels (8 spaces) + * from the start of its line. + * + * However, in the second case `bar` should only be indented by 4 spaces. This is because the offset of 1 indent level + * between the `(` and the `[` tokens gets "collapsed" because the two tokens are on the same line. As a result, the + * `(` token is mapped to the `[` token with an offset of 0, and the rule correctly decides that `bar` should be indented + * by 1 indent level from the start of the line. + * + * This is useful because rule listeners can usually just call `setDesiredOffset` for all the tokens in the node, + * without needing to check which lines those tokens are on. + * + * Note that since collapsing only occurs when two tokens are on the same line, there are a few cases where non-intuitive + * behavior can occur. For example, consider the following cases: + * + * foo( + * ). + * bar( + * baz + * ) + * + * foo( + * ).bar( + * baz + * ) + * + * Based on the first example, it would seem that `bar` should be offset by 1 indent level from `foo`, and `baz` + * should be offset by 1 indent level from `bar`. However, this is not correct, because it would result in `baz` + * being indented by 2 indent levels in the second case (since `foo`, `bar`, and `baz` are all on separate lines, no + * collapsing would occur). + * + * Instead, the correct way would be to offset `baz` by 1 level from `bar`, offset `bar` by 1 level from the `)`, and + * offset the `)` by 0 levels from `foo`. This ensures that the offset between `bar` and the `)` are correctly collapsed + * in the second case. + * + * @param {Token} token The token + * @param {Token} offsetFrom The token that `token` should be offset from + * @param {number} offset The desired indent level + * @returns {void} + */ setDesiredOffset(token, offsetFrom, offset) { if (offsetFrom && token.loc.start.line === offsetFrom.loc.start.line) { this.matchIndentOf(offsetFrom, token); diff --git a/tools/eslint/package-lock.json b/tools/eslint/package-lock.json index 4f78e2baa58b47..0718fa08a8a686 100644 --- a/tools/eslint/package-lock.json +++ b/tools/eslint/package-lock.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "4.1.0", + "version": "4.1.1", "lockfileVersion": 1, "dependencies": { "acorn": { diff --git a/tools/eslint/package.json b/tools/eslint/package.json index f66abf07fba861..50fb36855d0bcf 100644 --- a/tools/eslint/package.json +++ b/tools/eslint/package.json @@ -1,27 +1,27 @@ { - "_from": "eslint@4.1.0", - "_id": "eslint@4.1.0", + "_from": "eslint@latest", + "_id": "eslint@4.1.1", "_inBundle": false, - "_integrity": "sha1-u7VaKCIO4Itp2pVU1FprLr/X2RM=", + "_integrity": "sha1-+svfz+Pg+s06i4DcmMTmwTrlgt8=", "_location": "/eslint", "_phantomChildren": {}, "_requested": { - "type": "version", + "type": "tag", "registry": true, - "raw": "eslint@4.1.0", + "raw": "eslint@latest", "name": "eslint", "escapedName": "eslint", - "rawSpec": "4.1.0", + "rawSpec": "latest", "saveSpec": null, - "fetchSpec": "4.1.0" + "fetchSpec": "latest" }, "_requiredBy": [ "#USER", "/" ], - "_resolved": "https://registry.npmjs.org/eslint/-/eslint-4.1.0.tgz", - "_shasum": "bbb55a28220ee08b69da9554d45a6b2ebfd7d913", - "_spec": "eslint@4.1.0", + "_resolved": "https://registry.npmjs.org/eslint/-/eslint-4.1.1.tgz", + "_shasum": "facbdfcfe3e0facd3a8b80dc98c4e6c13ae582df", + "_spec": "eslint@latest", "_where": "/Users/trott/io.js/tools/eslint-tmp", "author": { "name": "Nicholas C. Zakas", @@ -152,5 +152,5 @@ "release": "node Makefile.js release", "test": "node Makefile.js test" }, - "version": "4.1.0" + "version": "4.1.1" }