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

refactor(watcher): refactor watcher.js #2979

Merged
merged 1 commit into from
May 4, 2018
Merged
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
130 changes: 52 additions & 78 deletions lib/watcher.js
Original file line number Diff line number Diff line change
@@ -1,118 +1,92 @@
var chokidar = require('chokidar')
var mm = require('minimatch')
var expandBraces = require('expand-braces')
'use strict'

var helper = require('./helper')
var log = require('./logger').create('watcher')
const chokidar = require('chokidar')
const mm = require('minimatch')
const expandBraces = require('expand-braces')

var DIR_SEP = require('path').sep
const helper = require('./helper')
const log = require('./logger').create('watcher')

// Get parent folder, that be watched (does not contain any special globbing character)
var baseDirFromPattern = function (pattern) {
const DIR_SEP = require('path').sep

function baseDirFromPattern (pattern) {
return pattern
.replace(/[/\\][^/\\]*\*.*$/, '') // remove parts with *
.replace(/[/\\][^/\\]*[!+]\(.*$/, '') // remove parts with !(...) and +(...)
.replace(/[/\\][^/\\]*\*.*$/, '') // remove parts with *
.replace(/[/\\][^/\\]*[!+]\(.*$/, '') // remove parts with !(...) and +(...)
.replace(/[/\\][^/\\]*\)\?.*$/, '') || DIR_SEP // remove parts with (...)?
}

var watchPatterns = function (patterns, watcher) {
// filter only unique non url patterns paths
var pathsToWatch = []
var uniqueMap = {}
var path
function watchPatterns (patterns, watcher) {
let pathsToWatch = new Set()

// expand ['a/{b,c}'] to ['a/b', 'a/c']
patterns = expandBraces(patterns)

patterns.forEach(function (pattern) {
path = baseDirFromPattern(pattern)
if (!uniqueMap[path]) {
uniqueMap[path] = true
pathsToWatch.push(path)
}
})
expandBraces(patterns)
.forEach(path => pathsToWatch.add(baseDirFromPattern(path)))

pathsToWatch = Array.from(pathsToWatch)
// watch only common parents, no sub paths
pathsToWatch.forEach(function (path) {
if (!pathsToWatch.some(function (p) {
return p !== path && path.substr(0, p.length + 1) === p + DIR_SEP
})) {
pathsToWatch.forEach(path => {
if (!pathsToWatch.some(p => p !== path && path.substr(0, p.length + 1) === p + DIR_SEP)) {
watcher.add(path)
log.debug('Watching "%s"', path)
}
})
}

// Function to test if a path should be ignored by chokidar.
var createIgnore = function (patterns, excludes) {
function checkAnyPathMatch (patterns, path) {
return patterns.some(pattern => mm(path, pattern, {dot: true}))
}

function createIgnore (patterns, excludes) {
return function (path, stat) {
if (!stat || stat.isDirectory()) {
return false
}

// Check if the path matches any of the watched patterns.
if (!patterns.some(function (pattern) {
return mm(path, pattern, {dot: true})
})) {
return true
}

// Check if the path matches any of the exclude patterns.
if (excludes.some(function (pattern) {
return mm(path, pattern, {dot: true})
})) {
return true
}

return false
return !checkAnyPathMatch(patterns, path) || checkAnyPathMatch(excludes, path)
}
}

var onlyWatchedTrue = function (pattern) {
return pattern.watched
}

var getWatchedPatterns = function (patternObjects) {
return patternObjects.filter(onlyWatchedTrue).map(function (patternObject) {
return patternObject.pattern
})
function getWatchedPatterns (patterns) {
return patterns
.reduce((array, pattern) => {
if (pattern.watched) {
array.push(pattern.pattern)
}
return array
}, [])
}

exports.watch = function (patterns, excludes, fileList, usePolling, emitter) {
var watchedPatterns = getWatchedPatterns(patterns)
var options = {
const watchedPatterns = getWatchedPatterns(patterns)

const watcher = new chokidar.FSWatcher({
usePolling: usePolling,
ignorePermissionErrors: true,
ignoreInitial: true,
ignored: createIgnore(watchedPatterns, excludes)
}
var chokidarWatcher = new chokidar.FSWatcher(options)
})

watchPatterns(watchedPatterns, chokidarWatcher)
watchPatterns(watchedPatterns, watcher)

var bind = function (fn) {
return function (path) {
return fn.call(fileList, helper.normalizeWinPath(path))
}
}
watcher
.on('add', path => fileList.addFile(helper.normalizeWinPath(path)))
.on('change', path => fileList.changeFile(helper.normalizeWinPath(path)))
.on('unlink', path => fileList.removeFile(helper.normalizeWinPath(path)))
.on('error', log.debug.bind(log))

// register events
chokidarWatcher.on('add', bind(fileList.addFile))
.on('change', bind(fileList.changeFile))
.on('unlink', bind(fileList.removeFile))
// If we don't subscribe; unhandled errors from Chokidar will bring Karma down
// (see GH Issue #959)
.on('error', function (e) {
log.debug(e)
})

emitter.on('exit', function (done) {
chokidarWatcher.close()
emitter.on('exit', done => {
watcher.close()
done()
})

return chokidarWatcher
return watcher
}

exports.watch.$inject = ['config.files', 'config.exclude', 'fileList', 'config.usePolling',
'emitter']
exports.watch.$inject = [
'config.files',
'config.exclude',
'fileList',
'config.usePolling',
'emitter'
]