Skip to content

Commit

Permalink
Merge pull request #48 from busykai/async-linting
Browse files Browse the repository at this point in the history
Async linting/.jshintrc config lookup
  • Loading branch information
cfjedimaster committed May 9, 2014
2 parents 5de1df5 + 6695cf2 commit 18c2938
Showing 1 changed file with 133 additions and 73 deletions.
206 changes: 133 additions & 73 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
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");

Expand All @@ -28,7 +29,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) {
Expand Down Expand Up @@ -70,34 +86,39 @@ define(function (require, exports, module) {


}

/**
* Loads project-wide JSHint configuration.
* Asynchronous linting entry point.
*
* JSHint project file should be located at <Project Root>/.jshintrc. It
* is loaded each time project is changed or the configuration file is
* modified.
* @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();
_loadConfig(fullPath)
.done(function (cfg) {
deferred.resolve(handleHinter(text, fullPath, cfg));
});
return deferred.promise();
}

/**
* 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 <a href="http://www.jshint.com/docs/options/">JSHint option
* reference</a>.
* @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) {
Expand All @@ -106,7 +127,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 {
Expand All @@ -115,6 +136,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 <a href="http://www.jshint.com/docs/options/">JSHint option
* reference</a>.
*/
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
Expand All @@ -138,55 +242,11 @@ 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();
}
});

$(ProjectManager)
.on("projectOpen.jshint", function () {
tryLoadConfig();
});

tryLoadConfig();

CodeInspection.register("javascript", {
name: "JSHint",
scanFile: handleHinter,
scanFileAsync: handleHinterAsync
});

});

0 comments on commit 18c2938

Please sign in to comment.