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

Issue #30 - add lint dirty files only flag #53

Merged
merged 12 commits into from
Jan 16, 2017

Conversation

sergesemashko
Copy link
Contributor

@sergesemashko sergesemashko commented Jan 4, 2017

@JaKXz, please, take a look

cc/ @athyuttamre

@coveralls
Copy link

coveralls commented Jan 4, 2017

Coverage Status

Coverage decreased (-22.6%) to 72.881% when pulling 4a54385 on sergesemashko:lintdirtyfiles into ffbab73 on vieron:master.

@JaKXz
Copy link
Contributor

JaKXz commented Jan 5, 2017

@sergesemashko thanks for taking a crack at this. My first question is from the coveralls failure: can you think of ways to test this?

Otherwise I like the way this is going.


var changedFiles = Object.keys(compilation.fileTimestamps).filter(function (watchfile) {
return (this.prevTimestamps[watchfile] || this.startTime) < (compilation.fileTimestamps[watchfile] || Infinity);
}.bind(this)).filter(minimatch.filter(globPatterb.join('|'), {matchBase: true}));
Copy link
Contributor

@JaKXz JaKXz Jan 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be re-writtern with a reduce on Object.keys(compilation.fileTimestamps)? it would be a bit clearer IMO and would be fewer calls/binds. changedFiles just needs to be an updated glob or array of globs that's passed to stylelint and ultimately to node-glob.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could even bring in lodash.reduce and split out this function into a private module in lib, since it's quite complex, and probably will have some compatibility issues with webpack 2 (#46).

Copy link
Contributor Author

@sergesemashko sergesemashko Jan 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, did not quite get where to put reduce. Would it be some like this?

function getChangedFiles(plugin, compilation, glob) {
    var updatedStyles = reduce(compilation.fileTimestamps, function (changedStyleFiles, timestamp, filename) {
      // Check if file has been changed first ...
      if ((plugin.prevTimestamps[filename] || plugin.startTime) < (compilation.fileTimestamps[filename] || Infinity)
        // ... then validate by the glob pattern.
        && minimatch(glob, {matchBase: true})
      ) {
        changedStyleFiles.push(filename);
      }
      return changedStyleFiles;
    }, []);
      
    return updatedStyles;
  }

An example without reduce:

function getChangedFiles(plugin, compilation, glob) {
    // Filter only changed files first ...
    var updated = Object.keys(compilation.fileTimestamps).filter(function (filename) {
      return (plugin.prevTimestamps[filename] || plugin.startTime) < (compilation.fileTimestamps[filename] || Infinity);
    });
    // ... then filter files by the glob pattern.
    var updatedStyles = updated.filter(minimatch.filter(glob, {matchBase: true}));
    return updatedStyles;
  }

The actual call would look like:

function apply(options, compiler) {
  var plugin = this;
  ...
  compiler.plugin('emit', function (compilation, callback) {
     ...
     var changedFiles = getChangedFiles(plugin, compilation, globPatterb.join('|'))
     ...
  }
  ...
}

@JaKXz, Let me what option you'd prefer to go with

Copy link
Contributor

@JaKXz JaKXz Jan 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sergesemashko I prefer the slightly purer first version, but I would use .concat instead of .push. I would also just return the result of reduce(obj, fn, []).

Another nitpick: would it be clearer to check if compilation.fileTimestamps[filename] is falsy? Probably not, but what do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, the emit function should be bound to this so that it's consistent with the other lib functions.

Copy link
Contributor Author

@sergesemashko sergesemashko Jan 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reduce iterates through the initial keys of compilation.fileTimestamps, so filename is the key of each entry and is there for sure 😉

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 let's see how the implementation goes and discuss further there :)

@@ -32,6 +32,7 @@
"dependencies": {
"arrify": "^1.0.1",
"chalk": "^1.1.3",
"minimatch": "^3.0.3",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use yarn add minimatch so the yarn.lock file is updated.

Copy link
Contributor

@JaKXz JaKXz Jan 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sergesemashko just curious, is the filter fn that you're using from minimatch available from node-glob? could remove unnecessary dependencies :)

Copy link
Contributor Author

@sergesemashko sergesemashko Jan 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch. Minimatch is actually a dependency of node-glob which doesn't export any kind of function to use here :( But I ran yarn add minimatch, surprisingly, I did not cause any updates of yarn.lock, because latest minimatch version is already installed in ./node_modules.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

@sergesemashko
Copy link
Contributor Author

@JaKXz, thanks for the feedback.
As this new piece of code is executed after initial webpack run the only one idea I have to test it is to change file contents programmatically in test case or create new file after initial webpack run. I'll take a look and see where I can get with this.

sergesemashko added 3 commits January 8, 2017 09:01
 - Add tests for lintDirtyFilesOnly flag
…nto lintdirtyfiles

# Conflicts:
#	package.json
#	test/index.js
#	yarn.lock
@sergesemashko
Copy link
Contributor Author

  • Separate logic for extracting changed files
  • Add tests for lintDirtyFilesOnly

@JaKXz, please, take a look once again.

@coveralls
Copy link

coveralls commented Jan 8, 2017

Coverage Status

Changes Unknown when pulling f3e08b9 on sergesemashko:lintdirtyfiles into ** on vieron:master**.

} else
if (runsCount === 2) {
// Check that on JS file change styleling is not triggered.
expect(stats.compilation.errors).to.have.length(0);
Copy link
Contributor Author

@sergesemashko sergesemashko Jan 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JaKXz, initially, I had separate test for testing non-style file change. But seems like it's not possible to run multiple webpack watch instances simultaneously as tests are executed in parallel mode. So, I had to squash all watch cases into one.

Copy link
Contributor

@JaKXz JaKXz Jan 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just mock out the complexity and assert that we are seeing the right thing? I don't want to test webpack (the framework).

It's definitely going to be easier to write a unit test for get-changed-files. I would start there in a new test file, and we can come back to the integration test later.

Copy link
Contributor

@JaKXz JaKXz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work so far, we just need to iron out some details and then this should be mergeable :)


compiler.plugin('emit', function (compilation, callback) {
var dirtyOptions = assign({}, options);
var globPatterb = dirtyOptions.files;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: typo

compiler.plugin('emit', function (compilation, callback) {
var dirtyOptions = assign({}, options);
var globPatterb = dirtyOptions.files;
var changedFiles = getChangedFiles(this, compilation, globPatterb.join('|'));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you pass an object here, naming the parameters? At some point I'll rewrite this plugin in the latest language features and use destructuring. For now it'll make it clearer why these args are being passed in.

👍🏽 thanks for taking my comments well. Liking the way this is written :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call

@@ -0,0 +1,19 @@
'use strict';

var webpack = require('webpack');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rebase to capture our changes to support webpack 1&2.

} else
if (runsCount === 2) {
// Check that on JS file change styleling is not triggered.
expect(stats.compilation.errors).to.have.length(0);
Copy link
Contributor

@JaKXz JaKXz Jan 8, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just mock out the complexity and assert that we are seeing the right thing? I don't want to test webpack (the framework).

It's definitely going to be easier to write a unit test for get-changed-files. I would start there in a new test file, and we can come back to the integration test later.

@coveralls
Copy link

coveralls commented Jan 9, 2017

Coverage Status

Coverage decreased (-2.8%) to 92.754% when pulling e0f19c7 on sergesemashko:lintdirtyfiles into 9aab3fa on vieron:master.

@sergesemashko
Copy link
Contributor Author

@JaKXz, again, thanks for the feedback.
Didn't want to get rid of integration test completely (spent quite a lot of time on it:)), so I commented it out for later use with supporting comments.
Please, take a look once again.

*/
});

context('getChangedFiles', function () {
Copy link
Contributor

@JaKXz JaKXz Jan 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in its own file [since getChangedFiles is its own module]. 👍 looks great though 😄

- `../helpers/watch`
- `path`
// Webpack integration test.
it('lints only changed files in watch mode', function (done) {
Copy link
Contributor

@JaKXz JaKXz Jan 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be marked with it.skip so we don't have to commit commented out code. In fact it could be inside it's own context block. missed that above, my bad!

In that block we should stub out the parts of webpack that we can assume will operate as a black box.

Then, I believe the watchCallback can be spied on and assertions can be made on individual calls of that fn. I don't mean to prescribe sinon as a solution since other libraries exist (e.g. I prefer testdouble) but I just wanted to demonstrate that I think it's possible to write this test without all the imperative code and then we can merge it :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good call, I'll try to mock blackbox logic then

#stuff
{display: block
;;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another nitpick: please make sure your editor is using EditorConfig - thanks!

@@ -8,11 +8,11 @@ var formatter = require('stylelint').formatters.string;

// Modules
var runCompilation = require('./lib/run-compilation');
var getChangedFiles = require('./lib/get-chaged-files');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: typo

* against cached timestamps from previous run.
*
* @param plugin - stylelint-webpack-plugin this scopr
* @param compilation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the entire compilation object here? If I haven't misread the code looks like we're just using the fileTimestamps property so I would just accept that as a param.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are right, fileTimestamps should be enough

@JaKXz
Copy link
Contributor

JaKXz commented Jan 10, 2017

@sergesemashko one thing I would suggest in general is to try to extract as many operations into pure "components" or functions that can be easily tested (like get-changed-files). Then maintenance (e.g. my life) becomes much easier because we just have to take care of small tested units rather than maintain tests of the integration of this complex logic with webpack. Hope that helps!

@coveralls
Copy link

Coverage Status

Coverage increased (+1.09%) to 98.649% when pulling 4697c3e on sergesemashko:lintdirtyfiles into 96781a3 on JaKXz:master.

1 similar comment
@coveralls
Copy link

Coverage Status

Coverage increased (+1.09%) to 98.649% when pulling 4697c3e on sergesemashko:lintdirtyfiles into 96781a3 on JaKXz:master.

@sergesemashko
Copy link
Contributor Author

What's done:

  • extracted logic into separate LintDirtyModulesOnlyPlugin, so it can keep its own state for runs count and previous timestamps
  • installed testdouble.js
  • removed webpack integration testing
  • I hope, I've managed to create real "unit" tests :) (it was fun using testdouble)

@JaKXz, please, take a look once again

Copy link
Contributor

@JaKXz JaKXz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sergesemashko great work! 🥇

I'm going to merge this, would you mind installing stylelint-webpack-plugin#master in your project(s) and kick the tires with both the lintDirtyModulesOnly flag on and off?

@JaKXz JaKXz merged commit 7e60e9b into webpack-contrib:master Jan 16, 2017
@sergesemashko
Copy link
Contributor Author

@JaKXz, sure, I'll take a look either today or tomorrow

joshwiens pushed a commit that referenced this pull request Mar 31, 2018
* resolves #30 - add lint dirty files only flag

* Separate logic to extract changed style files.

 - Add tests for lintDirtyFilesOnly flag

* Added a test case for non style file change

* Add unit test for getChangedFiles

* Remove unused dependencies

* Quiet Stylelint during tests

* Refactore lint dirty modules into plugin in separate file

* Add testdouble

* Fix lint warnings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants