Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

webpackAlertFilters #24

Open
wants to merge 14 commits into
base: updateEslint
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 49 additions & 14 deletions HACKING.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,60 @@
# How to debug Jester
# How to debug jester

You attach a graphical debugger ([node-inspector]) to jester.
You attach a graphical debugger ([Node Inspector]) to jester.

- Install [node-inspector] somewhere. Doesn't matter where.
- Install [Node Inspector] somewhere. Doesn't matter where.

$ npm install node-inspector

- Start node-inspector.

$ node_modules/.bin/node-inspector
Node Inspector v0.7.4
Visit http://127.0.0.1:8080/debug?port=5858 to start debugging.

*node-inspector* now waits for the process-to-debug on port 5858, and for you on port 8080.

- Start jester in debug mode.

$ node --debug-brk node_modules/jester-tester/src/bin/jester-watch.js
$ node-debug node_modules/jester-tester/src/bin/jester-watch.js
debugger listening on port 5858

- Browse to http://127.0.0.1:8080/debug?port=5858.
- Node Inspector opens http://127.0.0.1:8080/debug?ws=127.0.0.1:8080&port=5858 in a browser.
- Place your breakpoints and press the play button to start running jester.

[node-inspector]: https://npmjs.org/package/node-inspector
[Node Inspector]: https://npmjs.org/package/node-inspector

# Suppressing webpack errors/warnings

Webpack can return three kinds of alerts (http://webpack.github.io/docs/node.js-api.html#error-handling):

- Fatal errors - webpack was not able to build.
- Soft errors - webpack was able to build, but it'll probably break if you try to run it.
- Warnings - webpack was able to build and it'll probably work, but there are some code paths that break.

Some soft errors / warnings are false positives,
in the sense that the scenario in which these cause a failure is guaranteed never to occur in practice.

You can tell jester to suppress these false positives by adding the following to your `jester.json`.

```json
{
...
"webpackAlertFilters": [
{
"severity": "softError" or "warning",
"name": "ModuleNotFoundError",
"justification": "Suppressing this alert is a good idea because ...",
"origin/rawRequest": "imports?process=>undefined!when",
"dependencies/0/request": "vertx"
},
{
"severity": "softError" or "warning",
"name": "CriticalDependenciesWarning",
"justification": "Suppressing this alert is a good idea because ...",
"origin/rawRequest": "localforage",
"origin/blocks/0/expr/type": "CallExpression"
}
]
}
```

Sadly, figuring out exactly which values to configure is not easy:
they're not visible in jester's console output.
To get a hold of them, [debug jester](#how-to-debug-jester) and place a breakpoint in `src/lib/handleWebpackResult.js`.
Then build some code that triggers the alert that you wish to suppress.
This will hit the breakpoint.
Inspect the contents of `stats.compilation.errors` and/or `stats.compilation.warnings`
to see which values you should add to your configuration.
25 changes: 17 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
**NOTE: you probably want the @next version of jester tester!**

You can install it using `npm install jester-tester@next`. I've been working on it for a while now and it has a bunch of needed improvements such as being able to configure webpack and eslint directly. However I'm currently beta testing it and I have yet to add a migration script. Eslint is doing a lot of breaking changes at the moment while they're ramping up for their 1.0 release so I won't make the @next version the default until after the 1.0 eslint release. I also might replace karma with testem before that.

But me and my team are using the @next version full time, so it probably won't delete your entire file system. I encourage new jester users to start with the @next version. Just be aware that you might have a non-automatic upgrades (usually changing some settings, renaming some properties) for new releases.

If you encounter anything: let me know at https://github.com/jauco/jester/issues !


#Jester

>Get your project tested and out there with minimal fuss.
Expand All @@ -15,7 +24,7 @@ The goal of Jester is to give you a bootstrap for integrating these tools so you
1. Install [node.js](http://nodejs.org/download/)
2. Create a directory for your app `mkdir myApp; cd myApp`
3. Add a basic machine readable description of your app `npm init`
4. Install jester from npm and save it into the development dependencies. Under windows you'll have to run this as administrator (required by jsdoc for creating symlinks):
4. Install jester from npm and save it into the development dependencies. Under windows you'll have to run this as administrator (required by jsdoc for creating symlinks):
`npm install --save-dev jester-tester`

## Creating a project
Expand Down Expand Up @@ -127,7 +136,7 @@ describe("Greetings", function() {
```

This test asserts that the function hello will return the string `hello tested world`.
The test will run upon saving it if jester-watch is running, or else when you
The test will run upon saving it if jester-watch is running, or else when you
execute jester-batch.

Jester will now run your tests in the browsers as specified in `jester.js`. Of
Expand All @@ -144,7 +153,7 @@ module.exports = hello;

##Dependency injection
Jester makes it easy to replace a 'require'd module in the source file with a
test-stub.
test-stub.

Let's say we have a file called src/lib/db.js
```javascript
Expand Down Expand Up @@ -196,15 +205,15 @@ what happens under the hood. It's not really magical.

## Generating documentation from source

Documentation will be generated from appropriately annotated sources by jsdoc and includes the syntax highlighted source code. See [usejsdoc](http://usejsdoc.org/) for how to annotate your code, especially relevant is [Document CommonJS Modules](http://usejsdoc.org/howto-commonjs-modules.html).
Documentation will be generated from appropriately annotated sources by jsdoc and includes the syntax highlighted source code. See [usejsdoc](http://usejsdoc.org/) for how to annotate your code, especially relevant is [Document CommonJS Modules](http://usejsdoc.org/howto-commonjs-modules.html).

There are many ways to export and document code. The recommended way to export functions is:

/**
* Deep clone an object
*
* @param {Object} obj - the object to clone
* @returns {Object} a deep clone of the original object
* @returns {Object} a deep clone of the original object
* @see {@link http://stackoverflow.com/questions/728360/most-elegant-way-to-clone-a-javascript-object}
*/
function clone(obj) {
Expand All @@ -215,8 +224,8 @@ There are many ways to export and document code. The recommended way to export f

This enables jsdoc to recognize that clone is a (static) function, the clone symbol will show up in stack traces and is fully supported by IE8.

The api documentation will be written to a directory specified by the `apiDocPath` setting in `jester.json`, which defaults to `./doc/api/`. You can set the configuration option `readme` to point to a file that is a markdown formatted readme which will be included in the generated documentation on the homepage.
The api documentation will be written to a directory specified by the `apiDocPath` setting in `jester.json`, which defaults to `./doc/api/`. You can set the configuration option `readme` to point to a file that is a markdown formatted readme which will be included in the generated documentation on the homepage.

The api documentation will be generated when you run `jester-batch` or `jester-doc`. The latter is a bit faster because it *only* runs jsdoc. The documentation is *not* automatically updated when running `jester-watch`.
The api documentation will be generated when you run `jester-batch` or `jester-doc`. The latter is a bit faster because it *only* runs jsdoc. The documentation is *not* automatically updated when running `jester-watch`.

An additional benefit of annotating your code with jsdoc style comments is that there are a number of tools such as ide's and compilers which can take advantages of the additional information contained in those comments.
An additional benefit of annotating your code with jsdoc style comments is that there are a number of tools such as ide's and compilers which can take advantages of the additional information contained in those comments.
2 changes: 1 addition & 1 deletion src/bin/jester-batch.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ var loadConfig = require("../lib/loadConfig"),

var config = loadConfig();

rebuildProject(config.webpackOptions, config.fullEntryGlob, config.webpackWarningFilters)
rebuildProject(config.webpackOptions, config.fullEntryGlob, config.webpackAlertFilters)
.then(function() {
if(config.srcPath && config.apiDocPath) {
return rebuildDocumentation(config.srcPath, config.apiDocPath, config.jsdocConf, config.readme);
Expand Down
4 changes: 2 additions & 2 deletions src/bin/jester-watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function runTests(path) {
console.log("No tests found for '" + path + "'");
return false;
}
return createTestFile(testFiles, config.srcPath, config.webpackOptions, config.karmaPath, config.webpackWarningFilters).then(function () {
return createTestFile(testFiles, config.srcPath, config.webpackOptions, config.karmaPath, config.webpackAlertFilters).then(function () {
return server.run();
});
});
Expand Down Expand Up @@ -65,7 +65,7 @@ function startWatching() {
}

if (filePath.length > 3 && filePath.substr(-3) === ".js") {
var build = rebuildProject(config.webpackOptions, config.fullEntryGlob, config.webpackWarningFilters);
var build = rebuildProject(config.webpackOptions, config.fullEntryGlob, config.webpackAlertFilters);
if (isReallyFileChangeEvent(changeType, fileCurrentStat, filePreviousStat)) {
when.join(build, runTests(filePath)).done(function(){});
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/lib/createTestFile.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ function createEntryModules(srcPath, filenames) {
return entryModules;
}

module.exports = function createTestFile(filenames, srcPath, webpackConfig, karmaPath, webpackWarningFilters) {
module.exports = function createTestFile(filenames, srcPath, webpackConfig, karmaPath, webpackAlertFilters) {
var config = Object.create(webpackConfig);
config.output = Object.create(webpackConfig.output);
config.output.path = karmaPath;
config.entry = createEntryModules(srcPath, filenames);//fixme output may be null
config.output = Object.create(config.output || {});
config.output.filename = "[name].karmatest.js";
return webpack(config).then(function(stats) {
return handleWebpackResult(stats, webpackWarningFilters);
return handleWebpackResult(stats, webpackAlertFilters);
});
};
135 changes: 81 additions & 54 deletions src/lib/handleWebpackResult.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
"use strict";
module.exports = function handleWebpackResult(stats, webpackWarningFilters) {
module.exports = function handleWebpackResult(stats, webpackAlertFilters) {
if (stats) {
if (filterConfigIsValid(webpackWarningFilters)) {
stats.compilation.warnings = filterWebpackWarnings(stats.compilation.warnings, webpackWarningFilters);
if (webpackAlertFilters && filterConfigIsValid(webpackAlertFilters)) {
var softErrorFilters = webpackAlertFilters.filter(function isSoftErrorFilter(filter) {
return filter.severity === "softError";
});
var warningFilters = webpackAlertFilters.filter(function isWarningFilter(filter) {
return filter.severity === "warning";
});
stats.compilation.errors = filterWebpackAlerts(stats.compilation.errors, softErrorFilters);
stats.compilation.warnings = filterWebpackAlerts(stats.compilation.warnings, warningFilters);
}
stats = stats.toJson();
}
Expand All @@ -18,7 +25,7 @@ module.exports = function handleWebpackResult(stats, webpackWarningFilters) {

var logErrorsByLine = function (errors) {
byLine(errors, function (err) {
console.log(" ", err, typeof err);
console.log(" ", err);
});
};

Expand All @@ -43,78 +50,98 @@ module.exports = function handleWebpackResult(stats, webpackWarningFilters) {

};

function filterConfigIsSupported(webpackWarningFilter) {
return "name" in webpackWarningFilter
&& "origin/rawRequest" in webpackWarningFilter
&& "dependencies/0/request" in webpackWarningFilter
&& Object.keys(webpackWarningFilter).length === 3 // That is, webpackWarningFilter contains no keys other than these three.
&& webpackWarningFilter.name === "ModuleNotFoundError";
function filterConfigIsSupported(webpackAlertFilter) {
if (webpackAlertFilter && webpackAlertFilter.name && webpackAlertFilter.justification) {
if (webpackAlertFilter.name === "ModuleNotFoundError") {
return "origin/rawRequest" in webpackAlertFilter
&& "dependencies/0/request" in webpackAlertFilter;
} else if (webpackAlertFilter.name === "CriticalDependenciesWarning") {
return "origin/rawRequest" in webpackAlertFilter
&& "origin/blocks/0/expr/type" in webpackAlertFilter;
} else {
return false;
}
} else {
return false;
}
}

/**
* Checks the sanity of the webpackWarningFilters-configuration.
* @param {Array} webpackWarningFilters - the filter configuration.
* Checks the sanity of the webpackAlertFilters-configuration.
* @param {Array} webpackAlertFilters - the filter configuration.
* @return {bool} whether it is sane
*/
function filterConfigIsValid(webpackWarningFilters) {
var result = true;
if (webpackWarningFilters) {
if (Array.isArray(webpackWarningFilters)) {
for (var i = 0; i < webpackWarningFilters.length; i++) {
if (!filterConfigIsSupported(webpackWarningFilters[i])) {
// This filter config isn't supported. Abort.
console.warn(
"config.webpackWarningFilters[" + i + "] must be an object similar to {\n" +
" 'name': 'ModuleNotFoundError',\n" +
" 'origin/rawRequest': 'imports?process=>undefined!when',\n" +
" 'dependencies/0/request': 'vertx'\n" +
"}."
);
result = false;
}
function filterConfigIsValid(webpackAlertFilters) {
var result;
if (Array.isArray(webpackAlertFilters)) {
result = true;
for (var i = 0; i < webpackAlertFilters.length; i++) {
if (!filterConfigIsSupported(webpackAlertFilters[i])) {
// This filter config isn't supported. Abort.
console.warn(
'config.webpackAlertFilters[' + i + '] must be an object similar to either\n' +
'{\n' +
' "severity": "softError" or "warning",\n' +
' "name": "ModuleNotFoundError",\n' +
' "justification": "Suppressing this alert is a good idea because ...",\n' +
' "origin/rawRequest": "imports?process=>undefined!when",\n' +
' "dependencies/0/request": "vertx"\n' +
'}\n' +
'or\n' +
'{\n' +
' "severity": "softError" or "warning",\n' +
' "name": "CriticalDependenciesWarning",\n' +
' "justification": "Suppressing this alert is a good idea because ...",\n' +
' "origin/rawRequest": "localforage",\n' +
' "origin/blocks/0/expr/type": "CallExpression"\n' +
'}\n'
);
result = false;
}
} else {
console.warn("config.webpackWarningFilters must be an array.");
result = false;
}
} else {
// No filters are configured. That's okay.
result = true;
console.warn("config.webpackAlertFilters must be an array.");
result = false;
}
return result;
}

function filterWebpackWarnings(unfilteredWarnings, webpackWarningFilters) {
if (!webpackWarningFilters) {
// No filters are configured. Use the complete, unfiltered list of warnings.
return unfilteredWarnings;
function filterWebpackAlerts(unfilteredAlerts, alertFilters) {
if (!alertFilters) {
// No filters are configured. Use the complete, unfiltered list of alerts.
return unfilteredAlerts;
}

// There's at least one filter and all filter configs are supported. Do the actual filtering.
var filteredWarnings = unfilteredWarnings.filter(function filterWarning(warning, index) {
if (warning.name !== "ModuleNotFoundError") {
// filterWebpackWarnings only supports filtering of ModuleNotFoundError-warnings.
// This is some other kind of warning. Leave it in the list, so that it IS shown in Jester's output.
return true;
}

for (var i = 0; i < webpackWarningFilters.length; i++) {
var webpackWarningFilter = webpackWarningFilters[i];
if (
warning.origin.rawRequest === webpackWarningFilter["origin/rawRequest"]
&& warning.dependencies.length > 0
&& warning.dependencies[0].request === webpackWarningFilter["dependencies/0/request"]
) {
// This warning matches one of the filters.
var filteredAlerts = unfilteredAlerts.filter(function isShown(alert) {
for (var i = 0; i < alertFilters.length; i++) {
var webpackAlertFilter = alertFilters[i];
var matches = matchers[webpackAlertFilter.name];
if (matches(alert, webpackAlertFilter)) {
// This alert matches one of the filters.
// Remove it from the list, so that it is NOT shown in Jester's output.
return false;
}
}

// This warning matches none of the filters.
// This alert matches none of the filters.
// Leave it in the list, so that it IS shown in Jester's output.
return true;
});

return filteredWarnings;
return filteredAlerts;
}

var matchers = {
ModuleNotFoundError: function matches(alert, webpackAlertFilter) {
return alert.name === "ModuleNotFoundError"
&& alert.origin.rawRequest === webpackAlertFilter["origin/rawRequest"]
&& alert.dependencies.length > 0
&& alert.dependencies[0].request === webpackAlertFilter["dependencies/0/request"];
},
CriticalDependenciesWarning: function matches(alert, webpackAlertFilter) {
return alert.name === "CriticalDependenciesWarning"
&& alert.origin.rawRequest === webpackAlertFilter["origin/rawRequest"]
&& alert.origin.blocks[0].expr.type === webpackAlertFilter["origin/blocks/0/expr/type"];
},
};
4 changes: 2 additions & 2 deletions src/lib/rebuildProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ function createEntryModules(featureFiles) {
return entryModules;
}

module.exports = function rebuildProject(webpackConfig, entryGlob, webpackWarningFilters) {
module.exports = function rebuildProject(webpackConfig, entryGlob, webpackAlertFilters) {
return glob(entryGlob)
.then(function (featureFiles) {
var config = Object.create(webpackConfig);
config.entry = createEntryModules(featureFiles);
return webpack(config);
})
.then(function (stats){
return handleWebpackResult(stats, webpackWarningFilters);
return handleWebpackResult(stats, webpackAlertFilters);
});
};
Loading