loqatevars
Version:
Locate JavaScript files with 'const' or 'process.env' usage in LLM-generated codebases
196 lines (164 loc) • 6.52 kB
JavaScript
const {
analyzeConstUsage,
findMatchingFiles,
findMatchingFilesDetailed,
validateDirectory
} = require('../lib/utils');
const fs = require('fs-extra');
const path = require('path');
const os = require('os');
const globby = require('globby').default;
jest.mock('globby', () => {
// Create the main globby function mock
const mockGlobby = jest.fn();
// Attach stream method directly to the function
mockGlobby.stream = jest.fn();
return {
__esModule: true,
default: mockGlobby,
globby: mockGlobby,
stream: mockGlobby.stream,
};
});
describe('analyzeConstUsage', () => {
test('should detect const variable declarations', () => {
const code = `const x = 10;`;
const result = analyzeConstUsage(code, 'test.js');
expect(result.variableConst).toBe(1);
expect(result.shouldFlag).toBe(true);
});
test('should ignore require imports', () => {
const code = `const fs = require('fs');`;
const result = analyzeConstUsage(code, 'test.js');
expect(result.importConst).toBe(1);
expect(result.variableConst).toBe(0);
expect(result.shouldFlag).toBe(false);
});
test('should detect process.env usage', () => {
const code = `console.log(process.env.USER);`;
const result = analyzeConstUsage(code, 'test.js');
expect(result.hasProcessEnv).toBe(true);
expect(result.shouldFlag).toBe(true);
});
test('detects env alias usage', () => {
const code = `const env = process.env; console.log(env.MY_VAR);`;
const result = analyzeConstUsage(code, 'test.js');
expect(result.hasProcessEnv).toBe(true);
});
test('should correctly parse files with ES6 import statements', () => {
const importPath = path.join(__dirname, '..', 'directory_test', 'importModule.js');
const code = fs.readFileSync(importPath, 'utf8');
const result = analyzeConstUsage(code, 'importModule.js');
expect(result.importConst).toBe(2);
});
});
describe('findMatchingFiles', () => {
let testDir;
beforeAll(() => {
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'find-match-'));
fs.writeFileSync(path.join(testDir, 'file1.js'), 'const x = 1;');
fs.writeFileSync(path.join(testDir, 'file2.js'), 'process.env.USER;');
fs.writeFileSync(path.join(testDir, 'ignore.js'), '// ignored');
});
afterAll(() => {
fs.removeSync(testDir);
});
test('should find files with const or process.env', async () => {
// Mock globby.stream to return test files
const mockStream = async function* () {
yield path.join(testDir, 'file1.js');
yield path.join(testDir, 'file2.js');
};
globby.stream.mockReturnValue(mockStream());
const result = await findMatchingFiles(testDir, ['ignore.js']);
expect(result).toEqual(expect.arrayContaining(['file1.js', 'file2.js']));
});
test('should return an empty array when no matching files', async () => {
const noMatchDir = path.join(__dirname, 'no-match-data');
fs.ensureDirSync(noMatchDir);
fs.writeFileSync(path.join(noMatchDir, 'a.js'), 'let a = 1;');
fs.writeFileSync(path.join(noMatchDir, 'b.js'), 'var b = 2;');
// Mock globby.stream to return test files
const mockStream = async function* () {
yield path.join(noMatchDir, 'a.js');
yield path.join(noMatchDir, 'b.js');
};
globby.stream.mockReturnValue(mockStream());
const result = await findMatchingFiles(noMatchDir, []);
expect(result).toEqual([]);
fs.removeSync(noMatchDir);
});
});
describe('findMatchingFilesDetailed', () => {
let testDir;
beforeAll(() => {
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'detailed-match-'));
fs.writeFileSync(path.join(testDir, 'file1.js'), 'const x = 1;');
fs.writeFileSync(path.join(testDir, 'file2.js'), 'process.env.USER;');
fs.writeFileSync(path.join(testDir, 'ignore.js'), '// ignored');
});
afterAll(() => {
fs.removeSync(testDir);
});
test('should return detailed results', async () => {
// Mock globby.stream to return test files
const mockStream = async function* () {
yield path.join(testDir, 'file1.js');
yield path.join(testDir, 'file2.js');
};
globby.stream.mockReturnValue(mockStream());
const result = await findMatchingFilesDetailed(testDir, ['ignore.js']);
expect(result.summary.scannedFiles).toBe(2);
expect(result.summary.matchingFiles).toBe(2);
const paths = result.matches.map(m => m.relativePath);
expect(paths).toContain('file1.js');
expect(paths).toContain('file2.js');
});
test('should report zero matches when none found', async () => {
const emptyDir = path.join(__dirname, 'no-match-detailed');
fs.ensureDirSync(emptyDir);
fs.writeFileSync(path.join(emptyDir, 'a.js'), 'function t() { return 1; }');
fs.writeFileSync(path.join(emptyDir, 'b.js'), 'var x = 3;');
// Mock globby.stream to return test files
const mockStream = async function* () {
yield path.join(emptyDir, 'a.js');
yield path.join(emptyDir, 'b.js');
};
globby.stream.mockReturnValue(mockStream());
const result = await findMatchingFilesDetailed(emptyDir, []);
expect(result.matches.length).toBe(0);
expect(result.summary.matchingFiles).toBe(0);
expect(result.summary.scannedFiles).toBe(2);
fs.removeSync(emptyDir);
});
});
describe('validateDirectory additional cases', () => {
test('should reject when path is a file', async () => {
const filePath = path.join(__dirname, 'tempfile');
fs.writeFileSync(filePath, 'data');
await expect(validateDirectory(filePath)).rejects.toThrow('PATH_NOT_DIRECTORY');
fs.removeSync(filePath);
});
test('should reject for non-string path', async () => {
await expect(validateDirectory(42)).rejects.toThrow('INVALID_DIRECTORY');
});
});
describe('analyzeConstUsage error handling', () => {
test('should throw AppError for invalid JavaScript', () => {
const badCode = 'const =';
expect(() => analyzeConstUsage(badCode, 'bad.js')).toThrow(
"Failed to parse bad.js: Both module and script parsing failed. Module error: Unexpected token (1:6), Script error: Unexpected token (1:6)"
);
});
});
describe('concurrency detection', () => {
test('defaults to 1 when os.cpus returns undefined', () => {
jest.resetModules();
const os = require('os');
const original = os.cpus;
os.cpus = () => undefined;
const { concurrency } = require('../lib/utils');
os.cpus = original;
expect(concurrency).toBe(1);
});
});