This commit is contained in:
eric sciple
2019-12-31 10:16:18 -05:00
committed by GitHub
parent a94e2440cb
commit a11539e1db
16 changed files with 3065 additions and 0 deletions

View File

@@ -0,0 +1,671 @@
import * as child from 'child_process'
import * as glob from '../src/glob'
import * as io from '../../io/src/io'
import * as path from 'path'
import {promises as fs} from 'fs'
const IS_WINDOWS = process.platform === 'win32'
/**
* These test focus on the ability of glob to find files
* and not on the pattern matching aspect
*/
describe('glob', () => {
beforeAll(async () => {
await io.rmRF(getTestTemp())
})
it('detects cycle', async () => {
// Create the following layout:
// <root>
// <root>/file
// <root>/symDir -> <root>
const root = path.join(getTestTemp(), 'detects-cycle')
await fs.mkdir(root, {recursive: true})
await fs.writeFile(path.join(root, 'file'), 'test file content')
await createSymlinkDir(root, path.join(root, 'symDir'))
const itemPaths = await glob.glob(root)
expect(itemPaths).toEqual([root, path.join(root, 'file')])
// todo: ? expect(itemPaths[2]).toBe(path.join(root, 'symDir'))
})
it('detects deep cycle starting from middle', async () => {
// Create the following layout:
// <root>
// <root>/file-under-root
// <root>/folder-a
// <root>/folder-a/file-under-a
// <root>/folder-a/folder-b
// <root>/folder-a/folder-b/file-under-b
// <root>/folder-a/folder-b/folder-c
// <root>/folder-a/folder-b/folder-c/file-under-c
// <root>/folder-a/folder-b/folder-c/sym-folder -> <root>
const root = path.join(
getTestTemp(),
'detects-deep-cycle-starting-from-middle'
)
await fs.mkdir(path.join(root, 'folder-a', 'folder-b', 'folder-c'), {
recursive: true
})
await fs.writeFile(
path.join(root, 'file-under-root'),
'test file under root contents'
)
await fs.writeFile(
path.join(root, 'folder-a', 'file-under-a'),
'test file under a contents'
)
await fs.writeFile(
path.join(root, 'folder-a', 'folder-b', 'file-under-b'),
'test file under b contents'
)
await fs.writeFile(
path.join(root, 'folder-a', 'folder-b', 'folder-c', 'file-under-c'),
'test file under c contents'
)
await createSymlinkDir(
root,
path.join(root, 'folder-a', 'folder-b', 'folder-c', 'sym-folder')
)
await fs.stat(
path.join(
root,
'folder-a',
'folder-b',
'folder-c',
'sym-folder',
'file-under-root'
)
)
const itemPaths = await glob.glob(path.join(root, 'folder-a', 'folder-b'))
expect(itemPaths).toEqual([
path.join(root, 'folder-a', 'folder-b'),
path.join(root, 'folder-a', 'folder-b', 'file-under-b'),
path.join(root, 'folder-a', 'folder-b', 'folder-c'),
path.join(root, 'folder-a', 'folder-b', 'folder-c', 'file-under-c'),
path.join(root, 'folder-a', 'folder-b', 'folder-c', 'sym-folder'),
path.join(
root,
'folder-a',
'folder-b',
'folder-c',
'sym-folder',
'file-under-root'
),
path.join(
root,
'folder-a',
'folder-b',
'folder-c',
'sym-folder',
'folder-a'
),
path.join(
root,
'folder-a',
'folder-b',
'folder-c',
'sym-folder',
'folder-a',
'file-under-a'
)
])
})
it('detects cycle starting from symlink', async () => {
// Create the following layout:
// <root>
// <root>/file
// <root>/symDir -> <root>
const root: string = path.join(
getTestTemp(),
'detects-cycle-starting-from-symlink'
)
await fs.mkdir(root, {recursive: true})
await fs.writeFile(path.join(root, 'file'), 'test file content')
await createSymlinkDir(root, path.join(root, 'symDir'))
await fs.stat(path.join(root, 'symDir'))
const itemPaths = await glob.glob(path.join(root, 'symDir'))
expect(itemPaths).toEqual([
path.join(root, 'symDir'),
path.join(root, 'symDir', 'file')
])
// todo: ? expect(itemPaths[2]).toBe(path.join(root, 'symDir', 'symDir'));
})
it('does not follow symlink when followSymbolicLink=false', async () => {
// Create the following layout:
// <root>
// <root>/realDir
// <root>/realDir/file
// <root>/symDir -> <root>/realDir
const root = path.join(
getTestTemp(),
'does-not-follow-symlink-when-follow-false'
)
await fs.mkdir(path.join(root, 'realDir'), {recursive: true})
await fs.writeFile(path.join(root, 'realDir', 'file'), 'test file content')
await createSymlinkDir(
path.join(root, 'realDir'),
path.join(root, 'symDir')
)
const itemPaths = await glob.glob(root, {followSymbolicLinks: false})
expect(itemPaths).toEqual([
root,
path.join(root, 'realDir'),
path.join(root, 'realDir', 'file'),
path.join(root, 'symDir')
])
})
it('does not follow symlink when search path is symlink and followSymbolicLink=false', async () => {
// Create the following layout:
// realDir
// realDir/file
// symDir -> realDir
const root = path.join(
getTestTemp(),
'does-not-follow-symlink-when-search-path-is-symlink-and-follow-false'
)
await fs.mkdir(path.join(root, 'realDir'), {recursive: true})
await fs.writeFile(path.join(root, 'realDir', 'file'), 'test file content')
await createSymlinkDir(
path.join(root, 'realDir'),
path.join(root, 'symDir')
)
const itemPaths = await glob.glob(path.join(root, 'symDir'), {
followSymbolicLinks: false
})
expect(itemPaths).toEqual([path.join(root, 'symDir')])
})
it('does not return broken symlink', async () => {
// Create the following layout:
// <root>
// <root>/brokenSym -> <root>/noSuch
// <root>/realDir
// <root>/realDir/file
// <root>/symDir -> <root>/realDir
const root = path.join(getTestTemp(), 'does-not-return-broken-symlink')
await fs.mkdir(root, {recursive: true})
await createSymlinkDir(
path.join(root, 'noSuch'),
path.join(root, 'brokenSym')
)
await fs.mkdir(path.join(root, 'realDir'), {recursive: true})
await fs.writeFile(path.join(root, 'realDir', 'file'), 'test file content')
await createSymlinkDir(
path.join(root, 'realDir'),
path.join(root, 'symDir')
)
const itemPaths = await glob.glob(root)
expect(itemPaths).toEqual([
root,
path.join(root, 'realDir'),
path.join(root, 'realDir', 'file'),
path.join(root, 'symDir'),
path.join(root, 'symDir', 'file')
])
})
it('does not return broken symlink when search path is broken symlink', async () => {
// Create the following layout:
// <root>
// <root>/brokenSym -> <root>/noSuch
const root = path.join(
getTestTemp(),
'does-not-return-broken-symlink-when-search-path-is-broken-symlink'
)
await fs.mkdir(root, {recursive: true})
const brokenSymPath = path.join(root, 'brokenSym')
await createSymlinkDir(path.join(root, 'noSuch'), brokenSymPath)
await fs.lstat(brokenSymPath)
const itemPaths = await glob.glob(brokenSymPath)
expect(itemPaths).toEqual([])
})
it('does not search paths that are not partial matches', async () => {
// Create the following layout:
// <root>
// <root>/realDir
// <root>/realDir/nested
// <root>/realDir/nested/file
// <root>/realDir2
// <root>/realDir2/nested2
// <root>/realDir2/nested2/symDir -> <root>/noSuch
const root = path.join(
getTestTemp(),
'does-not-search-paths-that-are-not-partial-matches'
)
await fs.mkdir(path.join(root, 'realDir', 'nested'), {recursive: true})
await fs.writeFile(
path.join(root, 'realDir', 'nested', 'file'),
'test file content'
)
await fs.mkdir(path.join(root, 'realDir2', 'nested2'), {recursive: true})
await createSymlinkDir(
path.join(root, 'noSuch'),
path.join(root, 'realDir2', 'nested2', 'symDir')
)
const options: glob.IGlobOptions = {
followSymbolicLinks: true,
omitBrokenSymbolicLinks: false
}
// Should throw
try {
await glob.glob(`${root}/*Dir*/*nested*/*`, options)
throw new Error('should not reach here')
} catch (err) {
expect(err.message).toMatch(/broken symbolic link/i)
}
// Not partial match
let itemPaths = await glob.glob(`${root}/*Dir/*nested*/*`, options)
expect(itemPaths).toEqual([path.join(root, 'realDir', 'nested', 'file')])
// Not partial match
itemPaths = await glob.glob(`${root}/*Dir*/*nested/*`, options)
expect(itemPaths).toEqual([path.join(root, 'realDir', 'nested', 'file')])
})
it('does not throw for broken symlinks that are not matches or partial matches', async () => {
// Create the following layout:
// <root>
// <root>/realDir
// <root>/realDir/file
// <root>/symDir -> <root>/noSuch
const root = path.join(
getTestTemp(),
'does-not-throw-for-broken-symlinks-that-are-not-matches-or-partial-matches'
)
await fs.mkdir(path.join(root, 'realDir'), {recursive: true})
await fs.writeFile(path.join(root, 'realDir', 'file'), 'test file content')
await createSymlinkDir(path.join(root, 'noSuch'), path.join(root, 'symDir'))
const options: glob.IGlobOptions = {
followSymbolicLinks: true,
omitBrokenSymbolicLinks: false
}
// Match should throw
try {
await glob.glob(`${root}/*`, options)
throw new Error('should not reach here')
} catch (err) {
expect(err.message).toMatch(/broken symbolic link/i)
}
// Partial match should throw
try {
await glob.glob(`${root}/*/*`, options)
throw new Error('should not reach here')
} catch (err) {
expect(err.message).toMatch(/broken symbolic link/i)
}
// Not match or partial match
const itemPaths = await glob.glob(`${root}/*eal*/*`, options)
expect(itemPaths).toEqual([path.join(root, 'realDir', 'file')])
})
it('follows symlink', async () => {
// Create the following layout:
// <root>
// <root>/realDir
// <root>/realDir/file
// <root>/symDir -> <root>/realDir
const root = path.join(getTestTemp(), 'follows-symlink')
await fs.mkdir(path.join(root, 'realDir'), {recursive: true})
await fs.writeFile(path.join(root, 'realDir', 'file'), 'test file content')
await createSymlinkDir(
path.join(root, 'realDir'),
path.join(root, 'symDir')
)
const itemPaths = await glob.glob(root)
expect(itemPaths).toEqual([
root,
path.join(root, 'realDir'),
path.join(root, 'realDir', 'file'),
path.join(root, 'symDir'),
path.join(root, 'symDir', 'file')
])
})
it('follows symlink when search path is symlink', async () => {
// Create the following layout:
// realDir
// realDir/file
// symDir -> realDir
const root = path.join(
getTestTemp(),
'follows-symlink-when-search-path-is-symlink'
)
await fs.mkdir(path.join(root, 'realDir'), {recursive: true})
await fs.writeFile(path.join(root, 'realDir', 'file'), 'test file content')
await createSymlinkDir(
path.join(root, 'realDir'),
path.join(root, 'symDir')
)
const itemPaths = await glob.glob(path.join(root, 'symDir'))
expect(itemPaths).toEqual([
path.join(root, 'symDir'),
path.join(root, 'symDir', 'file')
])
})
it('returns broken symlink when followSymbolicLinks=false', async () => {
// Create the following layout:
// <root>
// <root>/brokenSym -> <root>/noSuch
// <root>/realDir
// <root>/realDir/file
// <root>/symDir -> <root>/realDir
const root = path.join(
getTestTemp(),
'returns-broken-symlink-when-follow-false'
)
await fs.mkdir(root, {recursive: true})
await createSymlinkDir(
path.join(root, 'noSuch'),
path.join(root, 'brokenSym')
)
await fs.mkdir(path.join(root, 'realDir'), {recursive: true})
await fs.writeFile(path.join(root, 'realDir', 'file'), 'test file content')
await createSymlinkDir(
path.join(root, 'realDir'),
path.join(root, 'symDir')
)
const itemPaths = await glob.glob(root, {followSymbolicLinks: false})
expect(itemPaths).toEqual([
root,
path.join(root, 'brokenSym'),
path.join(root, 'realDir'),
path.join(root, 'realDir', 'file'),
path.join(root, 'symDir')
])
})
it('returns broken symlink when search path is broken symlink and followSymbolicLinks=false', async () => {
// Create the following layout:
// <root>
// <root>/brokenSym -> <root>/noSuch
const root = path.join(
getTestTemp(),
'returns-broken-symlink-when-search-path-is-broken-symlink-and-follow-false'
)
await fs.mkdir(root, {recursive: true})
const brokenSymPath = path.join(root, 'brokenSym')
await createSymlinkDir(path.join(root, 'noSuch'), brokenSymPath)
const itemPaths = await glob.glob(brokenSymPath, {
followSymbolicLinks: false
})
expect(itemPaths).toEqual([brokenSymPath])
})
it('returns depth first', async () => {
// Create the following layout:
// <root>/a-file
// <root>/b-folder
// <root>/b-folder/a-file
// <root>/b-folder/b-folder
// <root>/b-folder/b-folder/file
// <root>/b-folder/c-file
// <root>/c-file
const root = path.join(getTestTemp(), 'returns-depth-first')
await fs.mkdir(path.join(root, 'b-folder', 'b-folder'), {recursive: true})
await fs.writeFile(path.join(root, 'a-file'), 'test a-file content')
await fs.writeFile(
path.join(root, 'b-folder', 'a-file'),
'test b-folder/a-file content'
)
await fs.writeFile(
path.join(root, 'b-folder', 'b-folder', 'file'),
'test b-folder/b-folder/file content'
)
await fs.writeFile(
path.join(root, 'b-folder', 'c-file'),
'test b-folder/c-file content'
)
await fs.writeFile(path.join(root, 'c-file'), 'test c-file content')
const itemPaths = await glob.glob(root)
expect(itemPaths).toEqual([
root,
path.join(root, 'a-file'),
path.join(root, 'b-folder'),
path.join(root, 'b-folder', 'a-file'),
path.join(root, 'b-folder', 'b-folder'),
path.join(root, 'b-folder', 'b-folder', 'file'),
path.join(root, 'b-folder', 'c-file'),
path.join(root, 'c-file')
])
})
it('returns descendants', async () => {
// Create the following layout:
// <root>/file-1
// <root>/dir-1
// <root>/dir-1/file-2
// <root>/dir-1/dir-2
// <root>/dir-1/dir-2/file-3
const root = path.join(getTestTemp(), 'returns-descendants')
await fs.mkdir(path.join(root, 'dir-1', 'dir-2'), {recursive: true})
await fs.writeFile(path.join(root, 'file-1'), '')
await fs.writeFile(path.join(root, 'dir-1', 'file-2'), '')
await fs.writeFile(path.join(root, 'dir-1', 'dir-2', 'file-3'), '')
// When pattern ends with `/**/`
let pattern = `${root}${path.sep}**${path.sep}`
expect(
await glob.glob(pattern, {
implicitDescendants: false
})
).toHaveLength(3) // sanity check
expect(await glob.glob(pattern)).toEqual([
root,
path.join(root, 'dir-1'),
path.join(root, 'dir-1', 'dir-2'),
path.join(root, 'dir-1', 'dir-2', 'file-3'),
path.join(root, 'dir-1', 'file-2'),
path.join(root, 'file-1')
])
// When pattern ends with something other than `/**/`
pattern = `${root}${path.sep}**${path.sep}dir-?`
expect(
await glob.glob(pattern, {
implicitDescendants: false
})
).toHaveLength(2) // sanity check
expect(await glob.glob(pattern)).toEqual([
path.join(root, 'dir-1'),
path.join(root, 'dir-1', 'dir-2'),
path.join(root, 'dir-1', 'dir-2', 'file-3'),
path.join(root, 'dir-1', 'file-2')
])
})
it('returns directories only when trailing slash and implicit descendants false', async () => {
// Create the following layout:
// <root>/file-1
// <root>/dir-1
// <root>/dir-1/file-2
// <root>/dir-1/dir-2
// <root>/dir-1/dir-2/file-3
const root = path.join(
getTestTemp(),
'returns-directories-only-when-trailing-slash-and-implicit-descendants-false'
)
await fs.mkdir(path.join(root, 'dir-1', 'dir-2'), {recursive: true})
await fs.writeFile(path.join(root, 'file-1'), '')
await fs.writeFile(path.join(root, 'dir-1', 'file-2'), '')
await fs.writeFile(path.join(root, 'dir-1', 'dir-2', 'file-3'), '')
const pattern = `${root}${path.sep}**${path.sep}`
expect(await glob.glob(pattern)).toHaveLength(6) // sanity check
expect(
await glob.glob(pattern, {
implicitDescendants: false
})
).toEqual([
root,
path.join(root, 'dir-1'),
path.join(root, 'dir-1', 'dir-2')
])
})
it('returns empty when search path does not exist', async () => {
const itemPaths = await glob.glob(path.join(getTestTemp(), 'nosuch'))
expect(itemPaths).toEqual([])
})
it('returns hidden files', async () => {
// Create the following layout:
// <root>
// <root>/.emptyFolder
// <root>/.file
// <root>/.folder
// <root>/.folder/file
const root = path.join(getTestTemp(), 'returns-hidden-files')
await createHiddenDirectory(path.join(root, '.emptyFolder'))
await createHiddenDirectory(path.join(root, '.folder'))
await createHiddenFile(path.join(root, '.file'), 'test .file content')
await fs.writeFile(
path.join(root, '.folder', 'file'),
'test .folder/file content'
)
const itemPaths = await glob.glob(root)
expect(itemPaths).toEqual([
root,
path.join(root, '.emptyFolder'),
path.join(root, '.file'),
path.join(root, '.folder'),
path.join(root, '.folder', 'file')
])
})
it('returns normalized paths', async () => {
// Create the following layout:
// <root>/hello/world.txt
const root: string = path.join(getTestTemp(), 'returns-normalized-paths')
await fs.mkdir(path.join(root, 'hello'), {recursive: true})
await fs.writeFile(path.join(root, 'hello', 'world.txt'), '')
const itemPaths = await glob.glob(
`${root}${path.sep}${path.sep}${path.sep}hello`
)
expect(itemPaths).toEqual([
path.join(root, 'hello'),
path.join(root, 'hello', 'world.txt')
])
})
it('throws when match broken symlink and omitBrokenSymbolicLinks=false', async () => {
// Create the following layout:
// <root>
// <root>/brokenSym -> <root>/noSuch
const root = path.join(
getTestTemp(),
'throws-when-match-broken-symlink-and-omit-false'
)
await fs.mkdir(root, {recursive: true})
await createSymlinkDir(
path.join(root, 'noSuch'),
path.join(root, 'brokenSym')
)
try {
await glob.glob(root, {omitBrokenSymbolicLinks: false})
throw new Error('Expected tl.find to throw')
} catch (err) {
expect(err.message).toMatch(/broken symbolic link/)
}
})
it('throws when search path is broken symlink and omitBrokenSymbolicLinks=false', async () => {
// Create the following layout:
// <root>
// <root>/brokenSym -> <root>/noSuch
const root = path.join(
getTestTemp(),
'throws-when-search-path-is-broken-symlink-and-omit-false'
)
await fs.mkdir(root, {recursive: true})
const brokenSymPath = path.join(root, 'brokenSym')
await createSymlinkDir(path.join(root, 'noSuch'), brokenSymPath)
await fs.lstat(brokenSymPath)
try {
await glob.glob(brokenSymPath, {omitBrokenSymbolicLinks: false})
throw new Error('Expected tl.find to throw')
} catch (err) {
expect(err.message).toMatch(/broken symbolic link/)
}
})
})
async function createHiddenDirectory(dir: string): Promise<void> {
if (!path.basename(dir).match(/^\./)) {
throw new Error(`Expected dir '${dir}' to start with '.'.`)
}
await fs.mkdir(dir, {recursive: true})
if (IS_WINDOWS) {
const result = child.spawnSync('attrib.exe', ['+H', dir])
if (result.status !== 0) {
const message: string = (result.output || []).join(' ').trim()
throw new Error(
`Failed to set hidden attribute for directory '${dir}'. ${message}`
)
}
}
}
async function createHiddenFile(file: string, content: string): Promise<void> {
if (!path.basename(file).match(/^\./)) {
throw new Error(`Expected dir '${file}' to start with '.'.`)
}
await fs.mkdir(path.dirname(file), {recursive: true})
await fs.writeFile(file, content)
if (IS_WINDOWS) {
const result = child.spawnSync('attrib.exe', ['+H', file])
if (result.status !== 0) {
const message: string = (result.output || []).join(' ').trim()
throw new Error(
`Failed to set hidden attribute for file '${file}'. ${message}`
)
}
}
}
function getTestTemp(): string {
return path.join(__dirname, '_temp', 'glob')
}
/**
* Creates a symlink directory on OSX/Linux, and a junction point directory on Windows.
* A symlink directory is not created on Windows since it requires an elevated context.
*/
async function createSymlinkDir(real: string, link: string): Promise<void> {
if (IS_WINDOWS) {
await fs.symlink(real, link, 'junction')
} else {
await fs.symlink(real, link)
}
}

View File

@@ -0,0 +1,640 @@
import * as pathHelper from '../src/internal-path-helper'
const IS_WINDOWS = process.platform === 'win32'
describe('path-helper', () => {
it('dirname interprets directory name from paths', () => {
assertDirectoryName('', '.')
assertDirectoryName('.', '.')
assertDirectoryName('..', '.')
assertDirectoryName('hello', '.')
assertDirectoryName('hello/', '.')
assertDirectoryName('hello/world', 'hello')
if (IS_WINDOWS) {
// Removes redundant slashes
assertDirectoryName('C:\\\\hello\\\\\\world\\\\', 'C:\\hello')
assertDirectoryName('C://hello///world//', 'C:\\hello')
// Relative root:
assertDirectoryName('\\hello\\\\world\\\\again\\\\', '\\hello\\world')
assertDirectoryName('/hello///world//again//', '\\hello\\world')
// UNC:
assertDirectoryName('\\\\hello\\world\\again\\', '\\\\hello\\world')
assertDirectoryName(
'\\\\hello\\\\\\world\\\\again\\\\',
'\\\\hello\\world'
)
assertDirectoryName(
'\\\\\\hello\\\\\\world\\\\again\\\\',
'\\\\hello\\world'
)
assertDirectoryName(
'\\\\\\\\hello\\\\\\world\\\\again\\\\',
'\\\\hello\\world'
)
assertDirectoryName('//hello///world//again//', '\\\\hello\\world')
assertDirectoryName('///hello///world//again//', '\\\\hello\\world')
assertDirectoryName('/////hello///world//again//', '\\\\hello\\world')
// Relative:
assertDirectoryName('hello\\world', 'hello')
// Directory trimming
assertDirectoryName('a:/hello', 'a:\\')
assertDirectoryName('z:/hello', 'z:\\')
assertDirectoryName('A:/hello', 'A:\\')
assertDirectoryName('Z:/hello', 'Z:\\')
assertDirectoryName('C:/', 'C:\\')
assertDirectoryName('C:/hello', 'C:\\')
assertDirectoryName('C:/hello/', 'C:\\')
assertDirectoryName('C:/hello/world', 'C:\\hello')
assertDirectoryName('C:/hello/world/', 'C:\\hello')
assertDirectoryName('C:', 'C:')
assertDirectoryName('C:hello', 'C:')
assertDirectoryName('C:hello/', 'C:')
assertDirectoryName('C:hello/world', 'C:hello')
assertDirectoryName('C:hello/world/', 'C:hello')
assertDirectoryName('/', '\\')
assertDirectoryName('/hello', '\\')
assertDirectoryName('/hello/', '\\')
assertDirectoryName('/hello/world', '\\hello')
assertDirectoryName('/hello/world/', '\\hello')
assertDirectoryName('\\', '\\')
assertDirectoryName('\\hello', '\\')
assertDirectoryName('\\hello\\', '\\')
assertDirectoryName('\\hello\\world', '\\hello')
assertDirectoryName('\\hello\\world\\', '\\hello')
assertDirectoryName('//hello', '\\\\hello')
assertDirectoryName('//hello/', '\\\\hello')
assertDirectoryName('//hello/world', '\\\\hello\\world')
assertDirectoryName('//hello/world/', '\\\\hello\\world')
assertDirectoryName('\\\\hello', '\\\\hello')
assertDirectoryName('\\\\hello\\', '\\\\hello')
assertDirectoryName('\\\\hello\\world', '\\\\hello\\world')
assertDirectoryName('\\\\hello\\world\\', '\\\\hello\\world')
assertDirectoryName('//hello/world/again', '\\\\hello\\world')
assertDirectoryName('//hello/world/again/', '\\\\hello\\world')
assertDirectoryName('hello/world/', 'hello')
assertDirectoryName('hello/world/again', 'hello\\world')
assertDirectoryName('../../hello', '..\\..')
} else {
// Should not converts slashes
assertDirectoryName('/hello\\world', '/')
assertDirectoryName('/hello\\world/', '/')
assertDirectoryName('\\\\hello\\world\\again', '.')
assertDirectoryName('\\\\hello\\world/', '.')
assertDirectoryName('\\\\hello\\world/again', '\\\\hello\\world')
assertDirectoryName('hello\\world', '.')
assertDirectoryName('hello\\world/', '.')
// Should remove redundant slashes (rooted paths; UNC format not special)
assertDirectoryName('//hello', '/')
assertDirectoryName('//hello/world', '/hello')
assertDirectoryName('//hello/world/', '/hello')
assertDirectoryName('//hello//world//', '/hello')
assertDirectoryName('///hello////world///', '/hello')
// Should remove redundant slashes (relative paths)
assertDirectoryName('hello//world//again//', 'hello/world')
assertDirectoryName('hello///world///again///', 'hello/world')
// Directory trimming (Windows drive root format not special)
assertDirectoryName('C:/', '.')
assertDirectoryName('C:/hello', 'C:')
assertDirectoryName('C:/hello/', 'C:')
assertDirectoryName('C:/hello/world', 'C:/hello')
assertDirectoryName('C:/hello/world/', 'C:/hello')
assertDirectoryName('C:', '.')
assertDirectoryName('C:hello', '.')
assertDirectoryName('C:hello/', '.')
assertDirectoryName('C:hello/world', 'C:hello')
assertDirectoryName('C:hello/world/', 'C:hello')
// Directory trimming (rooted paths)
assertDirectoryName('/', '/')
assertDirectoryName('/hello', '/')
assertDirectoryName('/hello/', '/')
assertDirectoryName('/hello/world', '/hello')
assertDirectoryName('/hello/world/', '/hello')
// Directory trimming (relative paths)
assertDirectoryName('hello/world/', 'hello')
assertDirectoryName('hello/world/again', 'hello/world')
assertDirectoryName('../../hello', '../..')
}
})
it('ensureAbsoluteRoot roots paths', () => {
if (IS_WINDOWS) {
const currentDrive = process.cwd().substr(0, 2)
expect(currentDrive.match(/^[A-Z]:$/i)).toBeTruthy()
const otherDrive = currentDrive.toUpperCase().startsWith('C')
? 'D:'
: 'C:'
// Preserves relative pathing
assertEnsureAbsoluteRoot('C:/foo', '.', `C:/foo\\.`)
assertEnsureAbsoluteRoot('C:/foo/..', 'bar', `C:/foo/..\\bar`)
assertEnsureAbsoluteRoot('C:/foo', 'bar/../baz', `C:/foo\\bar/../baz`)
// Already rooted - drive root
assertEnsureAbsoluteRoot('D:\\', 'C:/', 'C:/')
assertEnsureAbsoluteRoot('D:\\', 'a:/hello', 'a:/hello')
assertEnsureAbsoluteRoot('D:\\', 'C:\\', 'C:\\')
assertEnsureAbsoluteRoot('D:\\', 'C:\\hello', 'C:\\hello')
// Already rooted - relative current drive root
expect(process.cwd().length).toBeGreaterThan(3) // sanity check not drive root
assertEnsureAbsoluteRoot(`${otherDrive}\\`, currentDrive, process.cwd())
assertEnsureAbsoluteRoot(
`${otherDrive}\\`,
`${currentDrive}hello`,
`${process.cwd()}\\hello`
)
assertEnsureAbsoluteRoot(
`${otherDrive}\\`,
`${currentDrive}hello/world`,
`${process.cwd()}\\hello/world`
)
assertEnsureAbsoluteRoot(
`${otherDrive}\\`,
`${currentDrive}hello\\world`,
`${process.cwd()}\\hello\\world`
)
// Already rooted - relative other drive root
assertEnsureAbsoluteRoot(
`${currentDrive}\\`,
otherDrive,
`${otherDrive}\\`
)
assertEnsureAbsoluteRoot(
`${currentDrive}\\`,
`${otherDrive}hello`,
`${otherDrive}\\hello`
)
assertEnsureAbsoluteRoot(
`${currentDrive}\\`,
`${otherDrive}hello/world`,
`${otherDrive}\\hello/world`
)
assertEnsureAbsoluteRoot(
`${currentDrive}\\`,
`${otherDrive}hello\\world`,
`${otherDrive}\\hello\\world`
)
// Already rooted - current drive root
assertEnsureAbsoluteRoot(`${otherDrive}\\`, '/', `${currentDrive}\\`)
assertEnsureAbsoluteRoot(
`${otherDrive}\\`,
'/hello',
`${currentDrive}\\hello`
)
assertEnsureAbsoluteRoot(`${otherDrive}\\`, '\\', `${currentDrive}\\`)
assertEnsureAbsoluteRoot(
`${otherDrive}\\`,
'\\hello',
`${currentDrive}\\hello`
)
// Already rooted - UNC
assertEnsureAbsoluteRoot('D:\\', '//machine/share', '//machine/share')
assertEnsureAbsoluteRoot(
'D:\\',
'\\\\machine\\share',
'\\\\machine\\share'
)
// Relative
assertEnsureAbsoluteRoot('D:/', 'hello', 'D:/hello')
assertEnsureAbsoluteRoot('D:/', 'hello/world', 'D:/hello/world')
assertEnsureAbsoluteRoot('D:\\', 'hello', 'D:\\hello')
assertEnsureAbsoluteRoot('D:\\', 'hello\\world', 'D:\\hello\\world')
assertEnsureAbsoluteRoot('D:/root', 'hello', 'D:/root\\hello')
assertEnsureAbsoluteRoot('D:/root', 'hello/world', 'D:/root\\hello/world')
assertEnsureAbsoluteRoot('D:\\root', 'hello', 'D:\\root\\hello')
assertEnsureAbsoluteRoot(
'D:\\root',
'hello\\world',
'D:\\root\\hello\\world'
)
assertEnsureAbsoluteRoot('D:/root/', 'hello', 'D:/root/hello')
assertEnsureAbsoluteRoot('D:/root/', 'hello/world', 'D:/root/hello/world')
assertEnsureAbsoluteRoot('D:\\root\\', 'hello', 'D:\\root\\hello')
assertEnsureAbsoluteRoot(
'D:\\root\\',
'hello\\world',
'D:\\root\\hello\\world'
)
} else {
// Preserves relative pathing
assertEnsureAbsoluteRoot('/foo', '.', `/foo/.`)
assertEnsureAbsoluteRoot('/foo/..', 'bar', `/foo/../bar`)
assertEnsureAbsoluteRoot('/foo', 'bar/../baz', `/foo/bar/../baz`)
// Already rooted
assertEnsureAbsoluteRoot('/root', '/', '/')
assertEnsureAbsoluteRoot('/root', '/hello', '/hello')
assertEnsureAbsoluteRoot('/root', '/hello/world', '/hello/world')
// Not already rooted - Windows style drive root
assertEnsureAbsoluteRoot('/root', 'C:/', '/root/C:/')
assertEnsureAbsoluteRoot('/root', 'C:/hello', '/root/C:/hello')
assertEnsureAbsoluteRoot('/root', 'C:\\', '/root/C:\\')
// Not already rooted - Windows style relative drive root
assertEnsureAbsoluteRoot('/root', 'C:', '/root/C:')
assertEnsureAbsoluteRoot('/root', 'C:hello/world', '/root/C:hello/world')
// Not already rooted - Windows style current drive root
assertEnsureAbsoluteRoot('/root', '\\', '/root/\\')
assertEnsureAbsoluteRoot(
'/root',
'\\hello\\world',
'/root/\\hello\\world'
)
// Not already rooted - Windows style UNC
assertEnsureAbsoluteRoot(
'/root',
'\\\\machine\\share',
'/root/\\\\machine\\share'
)
// Not already rooted - relative
assertEnsureAbsoluteRoot('/', 'hello', '/hello')
assertEnsureAbsoluteRoot('/', 'hello/world', '/hello/world')
assertEnsureAbsoluteRoot('/', 'hello\\world', '/hello\\world')
assertEnsureAbsoluteRoot('/root', 'hello', '/root/hello')
assertEnsureAbsoluteRoot('/root', 'hello/world', '/root/hello/world')
assertEnsureAbsoluteRoot('/root', 'hello\\world', '/root/hello\\world')
assertEnsureAbsoluteRoot('/root/', 'hello', '/root/hello')
assertEnsureAbsoluteRoot('/root/', 'hello/world', '/root/hello/world')
assertEnsureAbsoluteRoot('/root/', 'hello\\world', '/root/hello\\world')
assertEnsureAbsoluteRoot('/root\\', 'hello', '/root\\/hello')
assertEnsureAbsoluteRoot('/root\\', 'hello/world', '/root\\/hello/world')
assertEnsureAbsoluteRoot(
'/root\\',
'hello\\world',
'/root\\/hello\\world'
)
}
})
it('hasAbsoluteRoot detects absolute root', () => {
if (IS_WINDOWS) {
// Drive root
assertHasAbsoluteRoot('C:/', true)
assertHasAbsoluteRoot('a:/hello', true)
assertHasAbsoluteRoot('c:/hello', true)
assertHasAbsoluteRoot('z:/hello', true)
assertHasAbsoluteRoot('A:/hello', true)
assertHasAbsoluteRoot('C:/hello', true)
assertHasAbsoluteRoot('Z:/hello', true)
assertHasAbsoluteRoot('C:\\', true)
assertHasAbsoluteRoot('C:\\hello', true)
// Relative drive root
assertHasAbsoluteRoot('C:', false)
assertHasAbsoluteRoot('C:hello', false)
assertHasAbsoluteRoot('C:hello/world', false)
assertHasAbsoluteRoot('C:hello\\world', false)
// Current drive root
assertHasAbsoluteRoot('/', false)
assertHasAbsoluteRoot('/hello', false)
assertHasAbsoluteRoot('/hello/world', false)
assertHasAbsoluteRoot('\\', false)
assertHasAbsoluteRoot('\\hello', false)
assertHasAbsoluteRoot('\\hello\\world', false)
// UNC
assertHasAbsoluteRoot('//machine/share', true)
assertHasAbsoluteRoot('//machine/share/', true)
assertHasAbsoluteRoot('//machine/share/hello', true)
assertHasAbsoluteRoot('\\\\machine\\share', true)
assertHasAbsoluteRoot('\\\\machine\\share\\', true)
assertHasAbsoluteRoot('\\\\machine\\share\\hello', true)
// Relative
assertHasAbsoluteRoot('hello', false)
assertHasAbsoluteRoot('hello/world', false)
assertHasAbsoluteRoot('hello\\world', false)
} else {
// Root
assertHasAbsoluteRoot('/', true)
assertHasAbsoluteRoot('/hello', true)
assertHasAbsoluteRoot('/hello/world', true)
// Windows style drive root - false on OSX/Linux
assertHasAbsoluteRoot('C:/', false)
assertHasAbsoluteRoot('a:/hello', false)
assertHasAbsoluteRoot('c:/hello', false)
assertHasAbsoluteRoot('z:/hello', false)
assertHasAbsoluteRoot('A:/hello', false)
assertHasAbsoluteRoot('C:/hello', false)
assertHasAbsoluteRoot('Z:/hello', false)
assertHasAbsoluteRoot('C:\\', false)
assertHasAbsoluteRoot('C:\\hello', false)
// Windows style relative drive root - false on OSX/Linux
assertHasAbsoluteRoot('C:', false)
assertHasAbsoluteRoot('C:hello', false)
assertHasAbsoluteRoot('C:hello/world', false)
assertHasAbsoluteRoot('C:hello\\world', false)
// Windows style current drive root - false on OSX/Linux
assertHasAbsoluteRoot('\\', false)
assertHasAbsoluteRoot('\\hello', false)
assertHasAbsoluteRoot('\\hello\\world', false)
// Windows style UNC - false on OSX/Linux
assertHasAbsoluteRoot('\\\\machine\\share', false)
assertHasAbsoluteRoot('\\\\machine\\share\\', false)
assertHasAbsoluteRoot('\\\\machine\\share\\hello', false)
// Relative
assertHasAbsoluteRoot('hello', false)
assertHasAbsoluteRoot('hello/world', false)
assertHasAbsoluteRoot('hello\\world', false)
}
})
it('hasRoot detects root', () => {
if (IS_WINDOWS) {
// Drive root
assertHasRoot('C:/', true)
assertHasRoot('a:/hello', true)
assertHasRoot('c:/hello', true)
assertHasRoot('z:/hello', true)
assertHasRoot('A:/hello', true)
assertHasRoot('C:/hello', true)
assertHasRoot('Z:/hello', true)
assertHasRoot('C:\\', true)
assertHasRoot('C:\\hello', true)
// Relative drive root
assertHasRoot('C:', true)
assertHasRoot('C:hello', true)
assertHasRoot('C:hello/world', true)
assertHasRoot('C:hello\\world', true)
// Current drive root
assertHasRoot('/', true)
assertHasRoot('/hello', true)
assertHasRoot('/hello/world', true)
assertHasRoot('\\', true)
assertHasRoot('\\hello', true)
assertHasRoot('\\hello\\world', true)
// UNC
assertHasRoot('//machine/share', true)
assertHasRoot('//machine/share/', true)
assertHasRoot('//machine/share/hello', true)
assertHasRoot('\\\\machine\\share', true)
assertHasRoot('\\\\machine\\share\\', true)
assertHasRoot('\\\\machine\\share\\hello', true)
// Relative
assertHasRoot('hello', false)
assertHasRoot('hello/world', false)
assertHasRoot('hello\\world', false)
} else {
// Root
assertHasRoot('/', true)
assertHasRoot('/hello', true)
assertHasRoot('/hello/world', true)
// Windows style drive root - false on OSX/Linux
assertHasRoot('C:/', false)
assertHasRoot('a:/hello', false)
assertHasRoot('c:/hello', false)
assertHasRoot('z:/hello', false)
assertHasRoot('A:/hello', false)
assertHasRoot('C:/hello', false)
assertHasRoot('Z:/hello', false)
assertHasRoot('C:\\', false)
assertHasRoot('C:\\hello', false)
// Windows style relative drive root - false on OSX/Linux
assertHasRoot('C:', false)
assertHasRoot('C:hello', false)
assertHasRoot('C:hello/world', false)
assertHasRoot('C:hello\\world', false)
// Windows style current drive root - false on OSX/Linux
assertHasRoot('\\', false)
assertHasRoot('\\hello', false)
assertHasRoot('\\hello\\world', false)
// Windows style UNC - false on OSX/Linux
assertHasRoot('\\\\machine\\share', false)
assertHasRoot('\\\\machine\\share\\', false)
assertHasRoot('\\\\machine\\share\\hello', false)
// Relative
assertHasRoot('hello', false)
assertHasRoot('hello/world', false)
assertHasRoot('hello\\world', false)
}
})
it('normalizeSeparators normalizes slashes', () => {
if (IS_WINDOWS) {
// Drive-rooted
assertNormalizeSeparators('C:/', 'C:\\')
assertNormalizeSeparators('C:/hello', 'C:\\hello')
assertNormalizeSeparators('C:/hello/', 'C:\\hello\\')
assertNormalizeSeparators('C:\\', 'C:\\')
assertNormalizeSeparators('C:\\hello', 'C:\\hello')
assertNormalizeSeparators('C:', 'C:')
assertNormalizeSeparators('C:hello', 'C:hello')
assertNormalizeSeparators('C:hello/world', 'C:hello\\world')
assertNormalizeSeparators('C:hello\\world', 'C:hello\\world')
assertNormalizeSeparators('/', '\\')
assertNormalizeSeparators('/hello', '\\hello')
assertNormalizeSeparators('/hello/world', '\\hello\\world')
assertNormalizeSeparators('/hello//world', '\\hello\\world')
assertNormalizeSeparators('\\', '\\')
assertNormalizeSeparators('\\hello', '\\hello')
assertNormalizeSeparators('\\hello\\', '\\hello\\')
assertNormalizeSeparators('\\hello\\world', '\\hello\\world')
assertNormalizeSeparators('\\hello\\\\world', '\\hello\\world')
// UNC
assertNormalizeSeparators('//machine/share', '\\\\machine\\share')
assertNormalizeSeparators('//machine/share/', '\\\\machine\\share\\')
assertNormalizeSeparators(
'//machine/share/hello',
'\\\\machine\\share\\hello'
)
assertNormalizeSeparators('///machine/share', '\\\\machine\\share')
assertNormalizeSeparators('\\\\machine\\share', '\\\\machine\\share')
assertNormalizeSeparators('\\\\machine\\share\\', '\\\\machine\\share\\')
assertNormalizeSeparators(
'\\\\machine\\share\\hello',
'\\\\machine\\share\\hello'
)
assertNormalizeSeparators('\\\\\\machine\\share', '\\\\machine\\share')
// Relative
assertNormalizeSeparators('hello', 'hello')
assertNormalizeSeparators('hello/world', 'hello\\world')
assertNormalizeSeparators('hello//world', 'hello\\world')
assertNormalizeSeparators('hello\\world', 'hello\\world')
assertNormalizeSeparators('hello\\\\world', 'hello\\world')
} else {
// Rooted
assertNormalizeSeparators('/', '/')
assertNormalizeSeparators('/hello', '/hello')
assertNormalizeSeparators('/hello/world', '/hello/world')
assertNormalizeSeparators('//hello/world/', '/hello/world/')
// Backslash not converted
assertNormalizeSeparators('C:\\', 'C:\\')
assertNormalizeSeparators('C:\\\\hello\\\\', 'C:\\\\hello\\\\')
assertNormalizeSeparators('\\', '\\')
assertNormalizeSeparators('\\hello', '\\hello')
assertNormalizeSeparators('\\hello\\world', '\\hello\\world')
assertNormalizeSeparators('hello\\world', 'hello\\world')
// UNC not converted
assertNormalizeSeparators('\\\\machine\\share', '\\\\machine\\share')
// UNC not preserved
assertNormalizeSeparators('//machine/share', '/machine/share')
// Relative
assertNormalizeSeparators('hello', 'hello')
assertNormalizeSeparators('hello/////world', 'hello/world')
}
})
it('safeTrimTrailingSeparator safely trims trailing separator', () => {
assertSafeTrimTrailingSeparator('', '')
if (IS_WINDOWS) {
// Removes redundant slashes
assertSafeTrimTrailingSeparator(
'C:\\\\hello\\\\\\world\\\\',
'C:\\hello\\world'
)
assertSafeTrimTrailingSeparator('C://hello///world//', 'C:\\hello\\world')
// Relative root:
assertSafeTrimTrailingSeparator(
'\\hello\\\\world\\\\again\\\\',
'\\hello\\world\\again'
)
assertSafeTrimTrailingSeparator(
'/hello///world//again//',
'\\hello\\world\\again'
)
// UNC:
assertSafeTrimTrailingSeparator('\\\\hello\\world\\', '\\\\hello\\world')
assertSafeTrimTrailingSeparator(
'\\\\hello\\world\\\\',
'\\\\hello\\world'
)
assertSafeTrimTrailingSeparator(
'\\\\hello\\\\\\world\\\\again\\',
'\\\\hello\\world\\again'
)
assertSafeTrimTrailingSeparator('//hello/world/', '\\\\hello\\world')
assertSafeTrimTrailingSeparator('//hello/world//', '\\\\hello\\world')
assertSafeTrimTrailingSeparator(
'//hello//world//again/',
'\\\\hello\\world\\again'
)
// Relative:
assertSafeTrimTrailingSeparator('hello\\world\\', 'hello\\world')
// Slash trimming
assertSafeTrimTrailingSeparator('a:/hello/', 'a:\\hello')
assertSafeTrimTrailingSeparator('z:/hello', 'z:\\hello')
assertSafeTrimTrailingSeparator('C:/', 'C:\\')
assertSafeTrimTrailingSeparator('C:\\', 'C:\\')
assertSafeTrimTrailingSeparator('C:/hello/world', 'C:\\hello\\world')
assertSafeTrimTrailingSeparator('C:/hello/world/', 'C:\\hello\\world')
assertSafeTrimTrailingSeparator('C:', 'C:')
assertSafeTrimTrailingSeparator('C:hello/', 'C:hello')
assertSafeTrimTrailingSeparator('/', '\\')
assertSafeTrimTrailingSeparator('/hello/', '\\hello')
assertSafeTrimTrailingSeparator('\\', '\\')
assertSafeTrimTrailingSeparator('\\hello\\', '\\hello')
assertSafeTrimTrailingSeparator('//hello/', '\\\\hello')
assertSafeTrimTrailingSeparator('//hello/world', '\\\\hello\\world')
assertSafeTrimTrailingSeparator('//hello/world/', '\\\\hello\\world')
assertSafeTrimTrailingSeparator('\\\\hello', '\\\\hello')
assertSafeTrimTrailingSeparator('\\\\hello\\', '\\\\hello')
assertSafeTrimTrailingSeparator('\\\\hello\\world', '\\\\hello\\world')
assertSafeTrimTrailingSeparator('\\\\hello\\world\\', '\\\\hello\\world')
assertSafeTrimTrailingSeparator('hello/world/', 'hello\\world')
assertSafeTrimTrailingSeparator('hello/', 'hello')
assertSafeTrimTrailingSeparator('../../', '..\\..')
} else {
// Should not converts slashes
assertSafeTrimTrailingSeparator('/hello\\world', '/hello\\world')
assertSafeTrimTrailingSeparator('/hello\\world/', '/hello\\world')
assertSafeTrimTrailingSeparator('\\\\hello\\world/', '\\\\hello\\world')
assertSafeTrimTrailingSeparator('hello\\world/', 'hello\\world')
// Should remove redundant slashes (rooted paths; UNC format not special)
assertSafeTrimTrailingSeparator('//hello', '/hello')
assertSafeTrimTrailingSeparator('//hello/world', '/hello/world')
assertSafeTrimTrailingSeparator('//hello/world/', '/hello/world')
assertSafeTrimTrailingSeparator('//hello//world//', '/hello/world')
assertSafeTrimTrailingSeparator('///hello////world///', '/hello/world')
// Should remove redundant slashes (relative paths)
assertSafeTrimTrailingSeparator('hello//world//', 'hello/world')
assertSafeTrimTrailingSeparator('hello///world///', 'hello/world')
// Slash trimming (Windows drive root format not special)
assertSafeTrimTrailingSeparator('C:/', 'C:')
assertSafeTrimTrailingSeparator('C:/hello', 'C:/hello')
assertSafeTrimTrailingSeparator('C:/hello/', 'C:/hello')
assertSafeTrimTrailingSeparator('C:hello/', 'C:hello')
// Slash trimming (rooted paths)
assertSafeTrimTrailingSeparator('/', '/')
assertSafeTrimTrailingSeparator('/hello', '/hello')
assertSafeTrimTrailingSeparator('/hello/', '/hello')
assertSafeTrimTrailingSeparator('/hello/world/', '/hello/world')
// Slash trimming (relative paths)
assertSafeTrimTrailingSeparator('hello/world/', 'hello/world')
assertSafeTrimTrailingSeparator('../../', '../..')
}
})
})
function assertDirectoryName(itemPath: string, expected: string): void {
expect(pathHelper.dirname(itemPath)).toBe(expected)
}
function assertEnsureAbsoluteRoot(
root: string,
itemPath: string,
expected: string
): void {
expect(pathHelper.ensureAbsoluteRoot(root, itemPath)).toBe(expected)
}
function assertHasAbsoluteRoot(itemPath: string, expected: boolean): void {
expect(pathHelper.hasAbsoluteRoot(itemPath)).toBe(expected)
}
function assertHasRoot(itemPath: string, expected: boolean): void {
expect(pathHelper.hasRoot(itemPath)).toBe(expected)
}
function assertNormalizeSeparators(itemPath: string, expected: string): void {
expect(pathHelper.normalizeSeparators(itemPath)).toBe(expected)
}
function assertSafeTrimTrailingSeparator(
itemPath: string,
expected: string
): void {
expect(pathHelper.safeTrimTrailingSeparator(itemPath)).toBe(expected)
}

View File

@@ -0,0 +1,92 @@
import * as path from 'path'
import {Path} from '../src/internal-path'
const IS_WINDOWS = process.platform === 'win32'
describe('path', () => {
it('constructs from rooted path', () => {
assertPath(`/`, `${path.sep}`, [path.sep])
assertPath(`/foo`, `${path.sep}foo`, [path.sep, 'foo'])
if (IS_WINDOWS) {
assertPath(`C:\\foo`, `C:\\foo`, ['C:\\', 'foo'])
assertPath(`C:foo`, `C:foo`, ['C:', 'foo'])
assertPath(`\\\\foo\\bar\\baz`, `\\\\foo\\bar\\baz`, [
'\\\\foo\\bar',
'baz'
])
}
})
it('constructs from rooted segments', () => {
assertPath([`/`], `${path.sep}`, [path.sep])
assertPath([`/`, `foo`], `${path.sep}foo`, [path.sep, 'foo'])
if (IS_WINDOWS) {
assertPath([`C:\\`, `foo`], `C:\\foo`, ['C:\\', 'foo'])
assertPath([`C:`, `foo`], `C:foo`, ['C:', 'foo'])
assertPath([`\\\\foo\\bar`, `baz`], `\\\\foo\\bar\\baz`, [
'\\\\foo\\bar',
'baz'
])
}
})
it('constructs from relative path', () => {
assertPath(`foo`, `foo`, ['foo'])
assertPath(`foo/bar`, `foo${path.sep}bar`, ['foo', 'bar'])
})
it('constructs from relative segments', () => {
assertPath([`foo`], `foo`, ['foo'])
assertPath([`foo`, `bar`], `foo${path.sep}bar`, ['foo', 'bar'])
})
it('normalizes slashes', () => {
assertPath(
`/foo///bar${path.sep}${path.sep}${path.sep}baz`,
`${path.sep}foo${path.sep}bar${path.sep}baz`,
[path.sep, 'foo', 'bar', 'baz']
)
})
it('preserves relative pathing', () => {
assertPath(
'/foo/../bar/./baz',
`${path.sep}foo${path.sep}..${path.sep}bar${path.sep}.${path.sep}baz`,
[path.sep, 'foo', '..', 'bar', '.', 'baz']
)
})
it('trims unnecessary trailing slash', () => {
assertPath('/', path.sep, [path.sep])
assertPath('/foo/', `${path.sep}foo`, [path.sep, 'foo'])
assertPath('foo/', 'foo', ['foo'])
assertPath('foo/bar/', `foo${path.sep}bar`, ['foo', 'bar'])
if (IS_WINDOWS) {
assertPath('\\', '\\', ['\\'])
assertPath('C:\\', 'C:\\', ['C:\\'])
assertPath('C:\\foo\\', 'C:\\foo', ['C:\\', 'foo'])
assertPath('C:foo\\', 'C:foo', ['C:', 'foo'])
assertPath('\\\\computer\\share\\', '\\\\computer\\share', [
'\\\\computer\\share'
])
assertPath('\\\\computer\\share\\foo', '\\\\computer\\share\\foo', [
'\\\\computer\\share',
'foo'
])
assertPath('\\\\computer\\share\\foo\\', '\\\\computer\\share\\foo', [
'\\\\computer\\share',
'foo'
])
}
})
})
function assertPath(
itemPath: string | string[],
expectedPath: string,
expectedSegments: string[]
): void {
const actual = new Path(itemPath)
expect(actual.toString()).toBe(expectedPath)
expect(actual.segments).toEqual(expectedSegments)
}

View File

@@ -0,0 +1,190 @@
import * as path from 'path'
import * as patternHelper from '../src/internal-pattern-helper'
import {MatchKind} from '../src/internal-match-kind'
import {IS_WINDOWS} from '../../io/src/io-util'
describe('pattern-helper', () => {
it('getSearchPaths omits negate search paths', () => {
const root = IS_WINDOWS ? 'C:\\' : '/'
const patterns = patternHelper.parse(
[
`${root}search1/foo/**`,
`${root}search2/bar/**`,
`!${root}search3/baz/**`
],
patternHelper.getOptions()
)
const searchPaths = patternHelper.getSearchPaths(patterns)
expect(searchPaths).toEqual([
`${root}search1${path.sep}foo`,
`${root}search2${path.sep}bar`
])
})
it('getSearchPaths omits search path when ancestor is also a search path', () => {
if (IS_WINDOWS) {
const patterns = patternHelper.parse(
[
'C:\\Search1\\Foo\\**',
'C:\\sEARCH1\\fOO\\bar\\**',
'C:\\sEARCH1\\foo\\bar',
'C:\\Search2\\**',
'C:\\Search3\\Foo\\Bar\\**',
'C:\\sEARCH3\\fOO\\bAR\\**'
],
patternHelper.getOptions()
)
const searchPaths = patternHelper.getSearchPaths(patterns)
expect(searchPaths).toEqual([
'C:\\Search1\\Foo',
'C:\\Search2',
'C:\\Search3\\Foo\\Bar'
])
} else {
const patterns = patternHelper.parse(
[
'/search1/foo/**',
'/search1/foo/bar/**',
'/search2/foo/bar',
'/search2/**',
'/search3/foo/bar/**',
'/search3/foo/bar/**'
],
patternHelper.getOptions()
)
const searchPaths = patternHelper.getSearchPaths(patterns)
expect(searchPaths).toEqual([
'/search1/foo',
'/search2',
'/search3/foo/bar'
])
}
})
it('match supports interleaved exclude patterns', () => {
const root = IS_WINDOWS ? 'C:\\' : '/'
const itemPaths = [
`${root}solution1/proj1/proj1.proj`,
`${root}solution1/proj1/README.txt`,
`${root}solution1/proj2/proj2.proj`,
`${root}solution1/proj2/README.txt`,
`${root}solution1/solution1.sln`,
`${root}solution2/proj1/proj1.proj`,
`${root}solution2/proj1/README.txt`,
`${root}solution2/proj2/proj2.proj`,
`${root}solution2/proj2/README.txt`,
`${root}solution2/solution2.sln`
]
const patterns = patternHelper.parse(
[
`${root}**/*.proj`, // include all proj files
`${root}**/README.txt`, // include all README files
`!${root}**/solution2/**`, // exclude the solution 2 folder entirely
`${root}**/*.sln`, // include all sln files
`!${root}**/proj2/README.txt` // exclude proj2 README files
],
patternHelper.getOptions({implicitDescendants: false})
)
const matched = itemPaths.filter(
x => patternHelper.match(patterns, x) === MatchKind.All
)
expect(matched).toEqual([
`${root}solution1/proj1/proj1.proj`,
`${root}solution1/proj1/README.txt`,
`${root}solution1/proj2/proj2.proj`,
`${root}solution1/solution1.sln`,
`${root}solution2/solution2.sln`
])
})
it('match supports excluding directories', () => {
const root = IS_WINDOWS ? 'C:\\' : '/'
const itemPaths = [
root,
`${root}foo`,
`${root}foo/bar`,
`${root}foo/bar/baz`
]
const patterns = patternHelper.parse(
[
`${root}foo/**`, // include all files and directories
`!${root}foo/**/` // exclude directories
],
patternHelper.getOptions({implicitDescendants: false})
)
const matchKinds = itemPaths.map(x => patternHelper.match(patterns, x))
expect(matchKinds).toEqual([
MatchKind.None,
MatchKind.File,
MatchKind.File,
MatchKind.File
])
})
it('match supports including directories only', () => {
const root = IS_WINDOWS ? 'C:\\' : '/'
const itemPaths = [
root,
`${root}foo/`,
`${root}foo/bar`,
`${root}foo/bar/baz`
]
const patterns = patternHelper.parse(
[
`${root}foo/**/` // include directories only
],
patternHelper.getOptions({implicitDescendants: false})
)
const matchKinds = itemPaths.map(x => patternHelper.match(patterns, x))
expect(matchKinds).toEqual([
MatchKind.None,
MatchKind.Directory,
MatchKind.Directory,
MatchKind.Directory
])
})
it('parse skips comments', () => {
const patterns = patternHelper.parse(
['# comment 1', ' # comment 2', '!#hello-world.txt'],
patternHelper.getOptions({implicitDescendants: false})
)
expect(patterns).toHaveLength(1)
expect(patterns[0].negate).toBeTruthy()
expect(patterns[0].segments.reverse()[0]).toEqual('#hello-world.txt')
})
it('parse skips empty patterns', () => {
const patterns = patternHelper.parse(
['', ' ', 'hello-world.txt'],
patternHelper.getOptions({implicitDescendants: false})
)
expect(patterns).toHaveLength(1)
expect(patterns[0].segments.reverse()[0]).toEqual('hello-world.txt')
})
it('partialMatch skips negate patterns', () => {
const root = IS_WINDOWS ? 'C:\\' : '/'
const patterns = patternHelper.parse(
[
`${root}search1/foo/**`,
`${root}search2/bar/**`,
`!${root}search2/bar/**`,
`!${root}search3/baz/**`
],
patternHelper.getOptions({implicitDescendants: false})
)
expect(patternHelper.partialMatch(patterns, `${root}search1`)).toBeTruthy()
expect(
patternHelper.partialMatch(patterns, `${root}search1/foo`)
).toBeTruthy()
expect(patternHelper.partialMatch(patterns, `${root}search2`)).toBeTruthy()
expect(
patternHelper.partialMatch(patterns, `${root}search2/bar`)
).toBeTruthy()
expect(patternHelper.partialMatch(patterns, `${root}search3`)).toBeFalsy()
expect(
patternHelper.partialMatch(patterns, `${root}search3/bar`)
).toBeFalsy()
})
})

View File

@@ -0,0 +1,366 @@
import * as io from '../../io/src/io'
import * as path from 'path'
import {MatchKind} from '../src/internal-match-kind'
import {promises as fs} from 'fs'
// Mock 'os' before importing Pattern
/* eslint-disable import/first */
/* eslint-disable @typescript-eslint/promise-function-async */
// Note, @typescript-eslint/promise-function-async is a false positive due to the
// mock factory delegate which returns any. Fixed in a future version of jest.
jest.mock('os', () => jest.requireActual('os'))
const os = jest.requireMock('os')
import {Pattern} from '../src/internal-pattern'
jest.resetModuleRegistry()
const IS_WINDOWS = process.platform === 'win32'
describe('pattern', () => {
beforeAll(async () => {
await io.rmRF(getTestTemp())
})
it('counts leading negate markers', () => {
const actual = [
'/initial-includes/*.txt',
'!!/hello/two-negate-markers.txt',
'!!!!/hello/four-negate-markers.txt',
'!/initial-includes/one-negate-markers.txt',
'!!!/initial-includes/three-negate-markers.txt'
].map(x => new Pattern(x).negate)
expect(actual).toEqual([false, false, false, true, true])
})
it('escapes homedir', async () => {
const originalHomedir = os.homedir
const home = path.join(getTestTemp(), 'home-with-[and]')
await fs.mkdir(home, {recursive: true})
try {
os.homedir = () => home
const pattern = new Pattern('~/m*')
expect(pattern.searchPath).toBe(home)
expect(pattern.match(path.join(home, 'match'))).toBeTruthy()
expect(pattern.match(path.join(home, 'not-match'))).toBeFalsy()
} finally {
os.homedir = originalHomedir
}
})
it('escapes root', async () => {
const originalCwd = process.cwd()
const rootPath = path.join(getTestTemp(), 'cwd-with-[and]')
await fs.mkdir(rootPath, {recursive: true})
try {
process.chdir(rootPath)
// Relative
let pattern = new Pattern('m*')
expect(pattern.searchPath).toBe(rootPath)
expect(pattern.match(path.join(rootPath, 'match'))).toBeTruthy()
expect(pattern.match(path.join(rootPath, 'not-match'))).toBeFalsy()
if (IS_WINDOWS) {
const currentDrive = process.cwd().substr(0, 2)
expect(currentDrive.match(/^[A-Z]:$/i)).toBeTruthy()
// Relative current drive letter, e.g. C:m*
pattern = new Pattern(`${currentDrive}m*`)
expect(pattern.searchPath).toBe(rootPath)
expect(pattern.match(path.join(rootPath, 'match'))).toBeTruthy()
expect(pattern.match(path.join(rootPath, 'not-match'))).toBeFalsy()
// Relative current drive, e.g. \path\to\cwd\m*
pattern = new Pattern(
`${Pattern.globEscape(process.cwd().substr(2))}\\m*`
)
expect(pattern.searchPath).toBe(rootPath)
expect(pattern.match(path.join(rootPath, 'match'))).toBeTruthy()
expect(pattern.match(path.join(rootPath, 'not-match'))).toBeFalsy()
}
} finally {
process.chdir(originalCwd)
}
})
it('globstar matches immediately preceeding directory', () => {
const root = IS_WINDOWS ? 'C:\\' : '/'
const pattern = new Pattern(`${root}foo/bar/**`)
const actual = [
root,
`${root}foo`,
`${root}foo/bar`,
`${root}foo/bar/baz`
].map(x => pattern.match(x))
expect(actual).toEqual([
MatchKind.None,
MatchKind.None,
MatchKind.All,
MatchKind.All
])
})
it('is case insensitive match on Windows', () => {
const root = IS_WINDOWS ? 'C:\\' : '/'
const pattern = new Pattern(`${root}Foo/**/Baz`)
expect(pattern.match(`${root}Foo/Baz`)).toBe(MatchKind.All)
expect(pattern.match(`${root}Foo/bAZ`)).toBe(
IS_WINDOWS ? MatchKind.All : MatchKind.None
)
expect(pattern.match(`${root}fOO/Baz`)).toBe(
IS_WINDOWS ? MatchKind.All : MatchKind.None
)
expect(pattern.match(`${root}fOO/bar/bAZ`)).toBe(
IS_WINDOWS ? MatchKind.All : MatchKind.None
)
})
it('is case insensitive partial match on Windows', () => {
const root = IS_WINDOWS ? 'C:\\' : '/'
const pattern = new Pattern(`${root}Foo/Bar/**/Baz`)
expect(pattern.partialMatch(`${root}Foo`)).toBeTruthy()
expect(pattern.partialMatch(`${root}fOO`)).toBe(IS_WINDOWS ? true : false)
})
it('matches root', () => {
const pattern = new Pattern(IS_WINDOWS ? 'C:\\**' : '/**')
expect(pattern.match(IS_WINDOWS ? 'C:\\' : '/')).toBe(MatchKind.All)
})
it('partial matches root', () => {
if (IS_WINDOWS) {
let pattern = new Pattern('C:\\foo\\**')
expect(pattern.partialMatch('c:\\')).toBeTruthy()
pattern = new Pattern('c:\\foo\\**')
expect(pattern.partialMatch('C:\\')).toBeTruthy()
} else {
const pattern = new Pattern('/foo/**')
expect(pattern.partialMatch('/')).toBeTruthy()
}
})
it('replaces leading . segment', () => {
// Pattern is '.'
let pattern = new Pattern('.')
expect(pattern.match(process.cwd())).toBe(MatchKind.All)
expect(pattern.match(path.join(process.cwd(), 'foo'))).toBe(MatchKind.None)
// Pattern is './foo'
pattern = new Pattern('./foo')
expect(pattern.match(path.join(process.cwd(), 'foo'))).toBe(MatchKind.All)
expect(pattern.match(path.join(process.cwd(), 'bar'))).toBe(MatchKind.None)
// Pattern is '.foo'
pattern = new Pattern('.foo')
expect(pattern.match(path.join(process.cwd(), '.foo'))).toBe(MatchKind.All)
expect(pattern.match(path.join(process.cwd(), 'foo'))).toBe(MatchKind.None)
expect(pattern.match(`${process.cwd()}foo`)).toBe(MatchKind.None)
})
it('replaces leading ~ segment', async () => {
const homedir = os.homedir()
expect(homedir).toBeTruthy()
await fs.stat(homedir)
// Pattern is '~'
let pattern = new Pattern('~')
expect(pattern.match(homedir)).toBe(MatchKind.All)
expect(pattern.match(path.join(homedir, 'foo'))).toBe(MatchKind.None)
// Pattern is '~/foo'
pattern = new Pattern('~/foo')
expect(pattern.match(path.join(homedir, 'foo'))).toBe(MatchKind.All)
expect(pattern.match(path.join(homedir, 'bar'))).toBe(MatchKind.None)
// Pattern is '~foo'
pattern = new Pattern('~foo')
expect(pattern.match(path.join(process.cwd(), '~foo'))).toBe(MatchKind.All)
expect(pattern.match(path.join(homedir, 'foo'))).toBe(MatchKind.None)
expect(pattern.match(`${homedir}foo`)).toBe(MatchKind.None)
})
it('replaces leading relative root', () => {
if (IS_WINDOWS) {
const currentDrive = process.cwd().substr(0, 2)
expect(currentDrive.match(/^[A-Z]:$/i)).toBeTruthy()
const otherDrive = currentDrive.toUpperCase().startsWith('C')
? 'D:'
: 'C:'
expect(process.cwd().length).toBeGreaterThan(3) // sanity check not drive root
// Pattern is 'C:'
let pattern = new Pattern(currentDrive)
expect(pattern.match(process.cwd())).toBeTruthy()
expect(pattern.match(path.join(process.cwd(), 'foo'))).toBeFalsy()
// Pattern is 'C:foo'
pattern = new Pattern(`${currentDrive}foo`)
expect(pattern.match(path.join(process.cwd(), 'foo'))).toBeTruthy()
expect(pattern.match(path.join(process.cwd(), 'bar'))).toBeFalsy()
expect(pattern.match(`${currentDrive}\\foo`)).toBeFalsy()
// Pattern is 'X:'
pattern = new Pattern(otherDrive)
expect(pattern.match(`${otherDrive}\\`)).toBeTruthy()
expect(pattern.match(`${otherDrive}\\foo`)).toBeFalsy()
// Pattern is 'X:foo'
pattern = new Pattern(`${otherDrive}foo`)
expect(pattern.match(`${otherDrive}\\foo`)).toBeTruthy()
expect(pattern.match(`${otherDrive}\\bar`)).toBeFalsy()
// Pattern is '\\path\\to\\cwd'
pattern = new Pattern(`${process.cwd().substr(2)}\\foo`)
expect(pattern.match(path.join(process.cwd(), 'foo'))).toBeTruthy()
expect(pattern.match(path.join(process.cwd(), 'bar'))).toBeFalsy()
}
})
it('roots exclude pattern', () => {
const patternStrings = ['!hello.txt', '!**/world.txt']
const actual = patternStrings.map(x => new Pattern(x))
const expected = patternStrings
.map(x => x.substr(1))
.map(x => path.join(Pattern.globEscape(process.cwd()), x))
.map(x => `!${x}`)
.map(x => new Pattern(x))
expect(actual.map(x => x.negate)).toEqual([true, true])
expect(actual.map(x => x.segments)).toEqual(expected.map(x => x.segments))
})
it('roots include pattern', () => {
const patternStrings = ['hello.txt', '**/world.txt']
const actual = patternStrings.map(x => new Pattern(x))
const expected = patternStrings.map(
x => new Pattern(path.join(Pattern.globEscape(process.cwd()), x))
)
expect(actual.map(x => x.segments)).toEqual(expected.map(x => x.segments))
})
it('sets trailing separator', () => {
expect(new Pattern(' foo ').trailingSeparator).toBeFalsy()
expect(new Pattern(' /foo ').trailingSeparator).toBeFalsy()
expect(new Pattern('! /foo ').trailingSeparator).toBeFalsy()
expect(new Pattern(' /foo/* ').trailingSeparator).toBeFalsy()
expect(new Pattern(' /foo/** ').trailingSeparator).toBeFalsy()
expect(new Pattern(' \\foo ').trailingSeparator).toBeFalsy()
expect(new Pattern('! \\foo ').trailingSeparator).toBeFalsy()
expect(new Pattern(' \\foo\\* ').trailingSeparator).toBeFalsy()
expect(new Pattern(' \\foo\\** ').trailingSeparator).toBeFalsy()
expect(new Pattern(' foo/ ').trailingSeparator).toBeTruthy()
expect(new Pattern(' /foo/ ').trailingSeparator).toBeTruthy()
expect(new Pattern(' C:/foo/ ').trailingSeparator).toBeTruthy()
expect(new Pattern(' C:foo/ ').trailingSeparator).toBeTruthy()
expect(new Pattern(' D:foo/ ').trailingSeparator).toBeTruthy()
expect(new Pattern('! /foo/ ').trailingSeparator).toBeTruthy()
expect(new Pattern(' /foo/*/ ').trailingSeparator).toBeTruthy()
expect(new Pattern(' /foo/**/ ').trailingSeparator).toBeTruthy()
expect(new Pattern(' foo\\ ').trailingSeparator).toEqual(
IS_WINDOWS ? true : false
)
expect(new Pattern(' \\foo\\ ').trailingSeparator).toEqual(
IS_WINDOWS ? true : false
)
expect(new Pattern('! \\foo\\ ').trailingSeparator).toEqual(
IS_WINDOWS ? true : false
)
expect(new Pattern(' \\foo\\*\\ ').trailingSeparator).toEqual(
IS_WINDOWS ? true : false
)
expect(new Pattern(' \\foo\\**\\ ').trailingSeparator).toEqual(
IS_WINDOWS ? true : false
)
})
it('supports including directories only', () => {
const root = IS_WINDOWS ? 'C:\\' : '/'
const pattern = new Pattern(`${root}foo/**/`) // trailing slash
const actual = [
root,
`${root}foo/`,
`${root}foo/bar`,
`${root}foo/bar/baz`
].map(x => pattern.match(x))
expect(pattern.trailingSeparator).toBeTruthy()
expect(actual).toEqual([
MatchKind.None,
MatchKind.Directory,
MatchKind.Directory,
MatchKind.Directory
])
})
it('trims pattern', () => {
const pattern = new Pattern(' hello.txt ')
expect(pattern.segments.reverse()[0]).toBe('hello.txt')
})
it('trims whitespace after trimming negate markers', () => {
const pattern = new Pattern(' ! ! ! hello.txt ')
expect(pattern.negate).toBeTruthy()
expect(pattern.segments.reverse()[0]).toBe('hello.txt')
})
it('unescapes segments to narrow search path', () => {
// Positive
const root = IS_WINDOWS ? 'C:\\' : '/'
let pattern = new Pattern(`${root}foo/b[a]r/b*`)
expect(pattern.searchPath).toBe(`${root}foo${path.sep}bar`)
expect(pattern.match(`${root}foo/bar/baz`)).toBeTruthy()
pattern = new Pattern(`${root}foo/b[*]r/b*`)
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b*r`)
expect(pattern.match(`${root}foo/b*r/baz`)).toBeTruthy()
expect(pattern.match(`${root}foo/bar/baz`)).toBeFalsy()
pattern = new Pattern(`${root}foo/b[?]r/b*`)
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b?r`)
expect(pattern.match(`${root}foo/b?r/baz`)).toBeTruthy()
expect(pattern.match(`${root}foo/bar/baz`)).toBeFalsy()
pattern = new Pattern(`${root}foo/b[!]r/b*`)
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b!r`)
expect(pattern.match(`${root}foo/b!r/baz`)).toBeTruthy()
pattern = new Pattern(`${root}foo/b[[]ar/b*`)
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b[ar`)
expect(pattern.match(`${root}foo/b[ar/baz`)).toBeTruthy()
pattern = new Pattern(`${root}foo/b[]r/b*`)
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b[]r`)
expect(pattern.match(`${root}foo/b[]r/baz`)).toBeTruthy()
pattern = new Pattern(`${root}foo/b[r/b*`)
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b[r`)
expect(pattern.match(`${root}foo/b[r/baz`)).toBeTruthy()
pattern = new Pattern(`${root}foo/b]r/b*`)
expect(pattern.searchPath).toBe(`${root}foo${path.sep}b]r`)
expect(pattern.match(`${root}foo/b]r/baz`)).toBeTruthy()
if (!IS_WINDOWS) {
pattern = new Pattern('/foo/b\\[a]r/b*')
expect(pattern.searchPath).toBe(`${path.sep}foo${path.sep}b[a]r`)
expect(pattern.match('/foo/b[a]r/baz')).toBeTruthy()
pattern = new Pattern('/foo/b[\\!]r/b*')
expect(pattern.searchPath).toBe(`${path.sep}foo${path.sep}b!r`)
expect(pattern.match('/foo/b!r/baz')).toBeTruthy()
pattern = new Pattern('/foo/b[\\]]r/b*')
expect(pattern.searchPath).toBe(`${path.sep}foo${path.sep}b]r`)
expect(pattern.match('/foo/b]r/baz')).toBeTruthy()
pattern = new Pattern('/foo/b[\\a]r/b*')
expect(pattern.searchPath).toBe(`${path.sep}foo${path.sep}bar`)
expect(pattern.match('/foo/bar/baz')).toBeTruthy()
}
// Negative
pattern = new Pattern(`${root}foo/b[aA]r/b*`)
expect(pattern.searchPath).toBe(`${root}foo`)
pattern = new Pattern(`${root}foo/b[!a]r/b*`)
expect(pattern.searchPath).toBe(`${root}foo`)
if (IS_WINDOWS) {
pattern = new Pattern('C:/foo/b\\[a]r/b*')
expect(pattern.searchPath).toBe(`C:\\foo\\b\\ar`)
expect(pattern.match('C:/foo/b/ar/baz')).toBeTruthy()
pattern = new Pattern('C:/foo/b[\\!]r/b*')
expect(pattern.searchPath).toBe('C:\\foo\\b[\\!]r')
expect(pattern.match('C:/foo/b[undefined/!]r/baz')).toBeTruthy() // Note, "undefined" substr to accommodate a bug in Minimatch when nocase=true
}
})
})
function getTestTemp(): string {
return path.join(__dirname, '_temp', 'internal-pattern')
}