diff --git a/src/managers/tasks.spec.ts b/src/managers/tasks.spec.ts index 99f4b624..0f714df3 100644 --- a/src/managers/tasks.spec.ts +++ b/src/managers/tasks.spec.ts @@ -57,7 +57,20 @@ describe('Managers → Task', () => { assert.deepStrictEqual(actual, expected); }); - it('should return two tasks', () => { + it('should return two tasks when one of patterns contains reference to the parent directory', () => { + const expected = [ + tests.task.builder().base('..').positive('../*.md').build(), + tests.task.builder().base('.').positive('*').positive('a/*').negative('*.md').build() + ]; + + const actual = manager.convertPatternsToTasks(['*', 'a/*', '../*.md'], ['*.md'], /* dynamic */ true); + + console.dir(actual, { colors: true }); + + assert.deepStrictEqual(actual, expected); + }); + + it('should return two tasks when all patterns refers to the different base directories', () => { const expected = [ tests.task.builder().base('a').positive('a/*').negative('b/*.md').build(), tests.task.builder().base('b').positive('b/*').negative('b/*.md').build() diff --git a/src/managers/tasks.ts b/src/managers/tasks.ts index 9694058c..7746257b 100644 --- a/src/managers/tasks.ts +++ b/src/managers/tasks.ts @@ -23,18 +23,34 @@ export function generate(patterns: Pattern[], settings: Settings): Task[] { return staticTasks.concat(dynamicTasks); } +/** + * Returns tasks grouped by basic pattern directories. + * + * Patterns that can be found inside (`./`) and outside (`../`) the current directory are handled separately. + * This is necessary because directory traversal starts at the base directory and goes deeper. + */ export function convertPatternsToTasks(positive: Pattern[], negative: Pattern[], dynamic: boolean): Task[] { - const positivePatternsGroup = groupPatternsByBaseDirectory(positive); + const tasks: Task[] = []; - // When we have a global group – there is no reason to divide the patterns into independent tasks. - // In this case, the global task covers the rest. - if ('.' in positivePatternsGroup) { - const task = convertPatternGroupToTask('.', positive, negative, dynamic); + const patternsOutsideCurrentDirectory = utils.pattern.getPatternsOutsideCurrentDirectory(positive); + const patternsInsideCurrentDirectory = utils.pattern.getPatternsInsideCurrentDirectory(positive); - return [task]; + const outsideCurrentDirectoryGroup = groupPatternsByBaseDirectory(patternsOutsideCurrentDirectory); + const insideCurrentDirectoryGroup = groupPatternsByBaseDirectory(patternsInsideCurrentDirectory); + + tasks.push(...convertPatternGroupsToTasks(outsideCurrentDirectoryGroup, [], dynamic)); + + /* + * For the sake of reducing future accesses to the file system, we merge all tasks within the current directory + * into a global task, if at least one pattern refers to the root (`.`). In this case, the global task covers the rest. + */ + if ('.' in insideCurrentDirectoryGroup) { + tasks.push(convertPatternGroupToTask('.', patternsInsideCurrentDirectory, negative, dynamic)); + } else { + tasks.push(...convertPatternGroupsToTasks(insideCurrentDirectoryGroup, negative, dynamic)); } - return convertPatternGroupsToTasks(positivePatternsGroup, negative, dynamic); + return tasks; } export function getPositivePatterns(patterns: Pattern[]): Pattern[] { diff --git a/src/utils/pattern.spec.ts b/src/utils/pattern.spec.ts index 8b56187b..59983e14 100644 --- a/src/utils/pattern.spec.ts +++ b/src/utils/pattern.spec.ts @@ -257,6 +257,52 @@ describe('Utils → Pattern', () => { }); }); + describe('.getPatternsInsideCurrentDirectory', () => { + it('should return patterns', () => { + const expected: Pattern[] = ['.', './*', '*', 'a/*']; + + const actual = util.getPatternsInsideCurrentDirectory(['.', './*', '*', 'a/*', '..', '../*', './..', './../*']); + + assert.deepStrictEqual(actual, expected); + }); + }); + + describe('.getPatternsOutsideCurrentDirectory', () => { + it('should return patterns', () => { + const expected: Pattern[] = ['..', '../*', './..', './../*']; + + const actual = util.getPatternsOutsideCurrentDirectory(['.', './*', '*', 'a/*', '..', '../*', './..', './../*']); + + assert.deepStrictEqual(actual, expected); + }); + }); + + describe('.isPatternRelatedToParentDirectory', () => { + it('should be `false` when the pattern refers to the current directory', () => { + const actual = util.isPatternRelatedToParentDirectory('.'); + + assert.ok(!actual); + }); + + it('should be `true` when the pattern equals to `..`', () => { + const actual = util.isPatternRelatedToParentDirectory('..'); + + assert.ok(actual); + }); + + it('should be `true` when the pattern starts with `..` segment', () => { + const actual = util.isPatternRelatedToParentDirectory('../*'); + + assert.ok(actual); + }); + + it('should be `true` when the pattern starts with `./..` segment', () => { + const actual = util.isPatternRelatedToParentDirectory('./../*'); + + assert.ok(actual); + }); + }); + describe('.getBaseDirectory', () => { it('should returns base directory', () => { const expected = 'root'; diff --git a/src/utils/pattern.ts b/src/utils/pattern.ts index 9801e138..6190b66f 100644 --- a/src/utils/pattern.ts +++ b/src/utils/pattern.ts @@ -81,6 +81,32 @@ export function getPositivePatterns(patterns: Pattern[]): Pattern[] { return patterns.filter(isPositivePattern); } +/** + * Returns patterns that can be applied inside the current directory. + * + * @example + * // ['./*', '*', 'a/*'] + * getPatternsInsideCurrentDirectory(['./*', '*', 'a/*', '../*', './../*']) + */ +export function getPatternsInsideCurrentDirectory(patterns: Pattern[]): Pattern[] { + return patterns.filter((pattern) => !isPatternRelatedToParentDirectory(pattern)); +} + +/** + * Returns patterns to be expanded relative to (outside) the current directory. + * + * @example + * // ['../*', './../*'] + * getPatternsInsideCurrentDirectory(['./*', '*', 'a/*', '../*', './../*']) + */ +export function getPatternsOutsideCurrentDirectory(patterns: Pattern[]): Pattern[] { + return patterns.filter(isPatternRelatedToParentDirectory); +} + +export function isPatternRelatedToParentDirectory(pattern: Pattern): boolean { + return pattern.startsWith('..') || pattern.startsWith('./..'); +} + export function getBaseDirectory(pattern: Pattern): string { return globParent(pattern, { flipBackslashes: false }); }