From b9ddd015033450107dc8eadca23619402a5c6aee Mon Sep 17 00:00:00 2001 From: Arzhan Kinzhalin Date: Tue, 6 May 2014 16:48:24 -0300 Subject: [PATCH 1/3] Use async linting. --- main.js | 62 +++++++++++++++++++++++++-------------------------------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/main.js b/main.js index 85774f1..559763e 100644 --- a/main.js +++ b/main.js @@ -18,7 +18,8 @@ define(function (require, exports, module) { "options": {"undef": true}, "globals": {} }, - config = defaultConfig; + config = defaultConfig, + configLoading; require("jshint/jshint"); @@ -70,6 +71,23 @@ define(function (require, exports, module) { } + + function handleHinterAsync(text, fullPath) { + var deferred = new $.Deferred(); + if (!configLoading) { + configLoading = _loadProjectConfig(); + } + configLoading + .done(function (cfg) { + config = cfg; + deferred.resolve(handleHinter(text, fullPath)); + }) + .fail(function (err) { + config = defaultConfig; + deferred.resolve(handleHinter(text, fullPath)); + }); + return deferred.promise(); + } /** * Loads project-wide JSHint configuration. @@ -138,55 +156,29 @@ define(function (require, exports, module) { return str; } - /** - * Attempts to load project configuration file. - */ - function tryLoadConfig() { - /** - * Makes sure JSHint is re-ran when the config is reloaded - * - * This is a workaround due to some loading issues in Sprint 31. - * See bug for details: https://github.com/adobe/brackets/issues/5442 - */ - function _refreshCodeInspection() { - CodeInspection.toggleEnabled(); - CodeInspection.toggleEnabled(); - } - _loadProjectConfig() - .done(function (newConfig) { - config = newConfig; - }) - .fail(function () { - config = defaultConfig; - }) - .always(function () { - _refreshCodeInspection(); - }); - } AppInit.appReady(function () { - CodeInspection.register("javascript", { - name: "JSHint", - scanFile: handleHinter - }); - $(DocumentManager) .on("documentSaved.jshint documentRefreshed.jshint", function (e, document) { // if this project's JSHint config has been updated, reload if (document.file.fullPath === ProjectManager.getProjectRoot().fullPath + _configFileName) { - tryLoadConfig(); + configLoading = _loadProjectConfig(); } }); $(ProjectManager) .on("projectOpen.jshint", function () { - tryLoadConfig(); + configLoading = _loadProjectConfig(); }); - tryLoadConfig(); - }); + CodeInspection.register("javascript", { + name: "JSHint", + scanFile: handleHinter, + scanFileAsync: handleHinterAsync + }); + }); From 15c575bef93c32e12ee078f389281b700bdcd282 Mon Sep 17 00:00:00 2001 From: Arzhan Kinzhalin Date: Fri, 9 May 2014 11:03:51 -0300 Subject: [PATCH 2/3] Lookup and use configs (.jshintrc). For files outside the project root default config is used. For files inside the project, .jshintrc is looked up as node-cli jshint would. --- main.js | 175 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 122 insertions(+), 53 deletions(-) diff --git a/main.js b/main.js index 559763e..e2a9398 100644 --- a/main.js +++ b/main.js @@ -12,13 +12,14 @@ define(function (require, exports, module) { var AppInit = brackets.getModule("utils/AppInit"), CodeInspection = brackets.getModule("language/CodeInspection"), FileSystem = brackets.getModule("filesystem/FileSystem"), + FileSystemError = brackets.getModule("filesystem/FileSystemError"), + FileUtils = brackets.getModule("file/FileUtils"), ProjectManager = brackets.getModule("project/ProjectManager"), DocumentManager = brackets.getModule("document/DocumentManager"), defaultConfig = { "options": {"undef": true}, "globals": {} }, - config = defaultConfig, configLoading; require("jshint/jshint"); @@ -29,7 +30,22 @@ define(function (require, exports, module) { */ var _configFileName = ".jshintrc"; - function handleHinter(text, fullPath) { + /** + * Synchronous linting entry point. + * + * @param {string} text File contents. + * @param {string} fullPath Absolute path to the file. + * @param {object} config JSHint configuration object. + * + * @return {object} Results of code inspection. + */ + function handleHinter(text, fullPath, config) { + + // make sure that synchronous linter does not break + if (!config) { + config = defaultConfig; + } + var resultJH = JSHINT(text, config.options, config.globals); if (!resultJH) { @@ -72,50 +88,38 @@ define(function (require, exports, module) { } + /** + * Asynchronous linting entry point. + * + * @param {string} text File contents. + * @param {string} fullPath Absolute path to the file. + * + * @return {$.Promise} Promise to return results of code inspection. + */ function handleHinterAsync(text, fullPath) { var deferred = new $.Deferred(); - if (!configLoading) { - configLoading = _loadProjectConfig(); - } - configLoading + _loadConfig(fullPath) .done(function (cfg) { - config = cfg; - deferred.resolve(handleHinter(text, fullPath)); - }) - .fail(function (err) { - config = defaultConfig; - deferred.resolve(handleHinter(text, fullPath)); + deferred.resolve(handleHinter(text, fullPath, cfg)); }); return deferred.promise(); } - + /** - * Loads project-wide JSHint configuration. - * - * JSHint project file should be located at /.jshintrc. It - * is loaded each time project is changed or the configuration file is - * modified. + * Reads configuration file in the specified directory. Returns a promise for configuration object. * - * @return Promise to return JSHint configuration object. + * @param {string} dir absolute path to a directory. * - * @see JSHint option - * reference. + * @returns {$.Promise} a promise to return configuration object. */ - function _loadProjectConfig() { - - var projectRootEntry = ProjectManager.getProjectRoot(), - result = new $.Deferred(), - file, - config; - - if (!projectRootEntry) { - return result.reject().promise(); - } - - file = FileSystem.getFileForPath(projectRootEntry.fullPath + _configFileName); + function _readConfig(dir) { + var result = new $.Deferred(), + file; + file = FileSystem.getFileForPath(dir + _configFileName); file.read(function (err, content) { if (!err) { - var cfg = {}; + var cfg = {}, + config; try { config = JSON.parse(removeComments(content)); } catch (e) { @@ -124,7 +128,7 @@ define(function (require, exports, module) { return; } cfg.globals = config.globals || {}; - if (config.global) { delete config.globals; } + if (config.globals) { delete config.globals; } cfg.options = config; result.resolve(cfg); } else { @@ -133,6 +137,89 @@ define(function (require, exports, module) { }); return result.promise(); } + + /** + * Looks up the configuration file in the filesystem hierarchy and loads it. + * + * @param {string} dir Relative path to directory to start with. + * @param {function} proc Function to read and load configuration file. + * + * @returns {$.Promise} A promise for configuration. + */ + function _lookupAndLoad(root, dir, readConfig) { + var deferred = new $.Deferred(), + done = false, + cdir = dir, + file, + iter = { + next: function () { + if (done) { + return; + } + cdir = FileUtils.getDirectoryPath(cdir.substring(0, cdir.length-1)); + readConfig(root + cdir) + .then(function (cfg) { + this.stop(cfg); + }.bind(this)) + .fail(function () { + if (!cdir) { + this.stop(defaultConfig); + } + if (!done) { + this.next(); + } + }.bind(this)); + }, + stop: function (cfg) { + deferred.resolve(cfg); + done = true; + } + }; + iter.next(); + return deferred.promise(); + } + + /** + * Loads JSHint configuration for the specified file. + * + * The configuration file should have name .jshintrc. If the specified file is outside the + * current project root, then defaultConfiguration is used. Otherwise, the configuration file + * is looked up starting from the directory where the specified file is located, going up to + * the project root, but no further. + * + * @param {string} fullPath Absolute path for the file linted. + * + * @return {$.Promise} Promise to return JSHint configuration object. + * + * @see JSHint option + * reference. + */ + function _loadConfig(fullPath) { + + var projectRootEntry = ProjectManager.getProjectRoot(), + result = new $.Deferred(), + relPath, + file, + config; + + if (!projectRootEntry) { + return result.reject().promise(); + } + + // for files outside the project root, use default config + if (!ProjectManager.isWithinProject(fullPath)) { + result.resolve(defaultConfig); + return result.promise(); + } + + relPath = FileUtils.getDirectoryPath(ProjectManager.makeProjectRelativeIfPossible(fullPath)); + + _lookupAndLoad(projectRootEntry.fullPath, relPath, _readConfig) + .done(function (cfg) { + result.resolve(cfg); + }); + return result.promise(); + } /** * Removes JavaScript comments from a string by replacing @@ -157,24 +244,6 @@ define(function (require, exports, module) { return str; } - AppInit.appReady(function () { - - $(DocumentManager) - .on("documentSaved.jshint documentRefreshed.jshint", function (e, document) { - // if this project's JSHint config has been updated, reload - if (document.file.fullPath === - ProjectManager.getProjectRoot().fullPath + _configFileName) { - configLoading = _loadProjectConfig(); - } - }); - - $(ProjectManager) - .on("projectOpen.jshint", function () { - configLoading = _loadProjectConfig(); - }); - - }); - CodeInspection.register("javascript", { name: "JSHint", scanFile: handleHinter, From 6695cf2f379287ba594690f62b32b7b1aa11b841 Mon Sep 17 00:00:00 2001 From: Arzhan Kinzhalin Date: Fri, 9 May 2014 11:08:32 -0300 Subject: [PATCH 3/3] Clean up. --- main.js | 1 - 1 file changed, 1 deletion(-) diff --git a/main.js b/main.js index e2a9398..88bbb21 100644 --- a/main.js +++ b/main.js @@ -12,7 +12,6 @@ define(function (require, exports, module) { var AppInit = brackets.getModule("utils/AppInit"), CodeInspection = brackets.getModule("language/CodeInspection"), FileSystem = brackets.getModule("filesystem/FileSystem"), - FileSystemError = brackets.getModule("filesystem/FileSystemError"), FileUtils = brackets.getModule("file/FileUtils"), ProjectManager = brackets.getModule("project/ProjectManager"), DocumentManager = brackets.getModule("document/DocumentManager"),