105 lines
4.3 KiB
JavaScript
105 lines
4.3 KiB
JavaScript
import { strict as assert } from 'node:assert'
|
|
import { mkdtemp, mkdir, symlink, writeFile, rm } from 'node:fs/promises'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { test } from 'node:test'
|
|
|
|
import { DEFAULT_EXTENSIONS, extensionOf, globToRegExp, listCodeFiles, matchesAny } from '../lib/walk.js'
|
|
|
|
test('globToRegExp anchors and handles star and double-star', () => {
|
|
assert.equal(globToRegExp('src/*.ts').test('src/a.ts'), true)
|
|
assert.equal(globToRegExp('src/*.ts').test('src/nested/a.ts'), false)
|
|
assert.equal(globToRegExp('**/*.ts').test('a/b/c.ts'), true)
|
|
assert.equal(globToRegExp('**/*.ts').test('c.ts'), true)
|
|
assert.equal(globToRegExp('src/**').test('src/a/b/c.ts'), true)
|
|
assert.equal(globToRegExp('dist/').test('dist/a/b.js'), true)
|
|
assert.equal(globToRegExp('src/a?.ts').test('src/ab.ts'), true)
|
|
assert.equal(globToRegExp('src/a?.ts').test('src/abc.ts'), false)
|
|
// Dots must be literal, not wildcards.
|
|
assert.equal(globToRegExp('a.ts').test('axts'), false)
|
|
})
|
|
|
|
test('matchesAny ignores empty patterns', () => {
|
|
assert.equal(matchesAny('a/b.ts', ['']), false)
|
|
assert.equal(matchesAny('a/b.ts', ['', 'a/*.ts']), true)
|
|
})
|
|
|
|
test('extensionOf lowercases and rejects dotfiles', () => {
|
|
assert.equal(extensionOf('a/B.TS'), 'ts')
|
|
assert.equal(extensionOf('.gitignore'), '')
|
|
assert.equal(extensionOf('Makefile'), '')
|
|
})
|
|
|
|
test('listCodeFiles returns a deterministic sorted list of code files only', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'recall-walk-'))
|
|
try {
|
|
await mkdir(join(root, 'src', 'nested'), { recursive: true })
|
|
await mkdir(join(root, 'node_modules', 'dep'), { recursive: true })
|
|
await writeFile(join(root, 'src', 'b.ts'), 'export const b = 1\n')
|
|
await writeFile(join(root, 'src', 'a.ts'), 'export const a = 1\n')
|
|
await writeFile(join(root, 'src', 'nested', 'c.py'), 'c = 1\n')
|
|
await writeFile(join(root, 'README.md'), '# not code\n')
|
|
await writeFile(join(root, 'node_modules', 'dep', 'index.js'), 'module.exports = 1\n')
|
|
|
|
const { files } = await listCodeFiles(root)
|
|
assert.deepEqual(
|
|
files.map((file) => file.path),
|
|
['src/a.ts', 'src/b.ts', 'src/nested/c.py'],
|
|
)
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
test('listCodeFiles honours an extension filter and an exclude glob', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'recall-walk-'))
|
|
try {
|
|
await mkdir(join(root, 'src', 'generated'), { recursive: true })
|
|
await writeFile(join(root, 'src', 'a.ts'), 'a\n')
|
|
await writeFile(join(root, 'src', 'generated', 'b.ts'), 'b\n')
|
|
await writeFile(join(root, 'src', 'c.py'), 'c\n')
|
|
|
|
const onlyTs = await listCodeFiles(root, { extensions: ['ts'] })
|
|
assert.deepEqual(onlyTs.files.map((file) => file.path), ['src/a.ts', 'src/generated/b.ts'])
|
|
|
|
const excluded = await listCodeFiles(root, { exclude: ['src/generated/'] })
|
|
assert.deepEqual(excluded.files.map((file) => file.path), ['src/a.ts', 'src/c.py'])
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
test('listCodeFiles reports truncation instead of silently dropping files', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'recall-walk-'))
|
|
try {
|
|
for (let at = 0; at < 5; at += 1) await writeFile(join(root, `f${at}.ts`), `${at}\n`)
|
|
const { files, truncated } = await listCodeFiles(root, { maxFiles: 2 })
|
|
assert.equal(files.length, 2)
|
|
assert.equal(truncated, true)
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
test('listCodeFiles does not follow symlinks', async () => {
|
|
const root = await mkdtemp(join(tmpdir(), 'recall-walk-'))
|
|
const outside = await mkdtemp(join(tmpdir(), 'recall-outside-'))
|
|
try {
|
|
await writeFile(join(outside, 'secret.ts'), 'export const secret = 1\n')
|
|
await writeFile(join(root, 'real.ts'), 'export const real = 1\n')
|
|
await symlink(outside, join(root, 'linked'), 'dir')
|
|
|
|
const { files } = await listCodeFiles(root)
|
|
assert.deepEqual(files.map((file) => file.path), ['real.ts'])
|
|
} finally {
|
|
await rm(root, { recursive: true, force: true })
|
|
await rm(outside, { recursive: true, force: true })
|
|
}
|
|
})
|
|
|
|
test('the default extension set is code, not prose', () => {
|
|
assert.ok(DEFAULT_EXTENSIONS.includes('ts'))
|
|
assert.ok(DEFAULT_EXTENSIONS.includes('py'))
|
|
assert.ok(!DEFAULT_EXTENSIONS.includes('md'))
|
|
})
|