Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
08600a1
ENG-219: Add TBD check
d4mation Feb 12, 2026
658fc65
ENG-219: Add test for --dev flag using fail_method_dev on tbd check
d4mation Feb 12, 2026
c6c5d76
Merge branch 'ENG-219/command-check' into ENG-219/command-check-tbd
d4mation Feb 12, 2026
8a9aea6
Merge branch 'ENG-219/command-check' into ENG-219/command-check-tbd
d4mation Feb 13, 2026
a955db7
ENG-219: Add test for running only tbd check without version-conflict
d4mation Feb 13, 2026
45ccc71
ENG-219: Move tbd-only check test from check.test.ts to tbd.test.ts
d4mation Feb 13, 2026
ea1e845
Merge branch 'ENG-219/command-check' into ENG-219/command-check-tbd
d4mation Feb 13, 2026
00a5399
Merge branch 'ENG-219/command-check' into ENG-219/command-check-tbd
d4mation Feb 13, 2026
3d40eb0
Merge branch 'ENG-219/command-check' into ENG-219/command-check-tbd
d4mation Feb 13, 2026
f6d5a93
Merge branch 'ENG-219/command-check' into ENG-219/command-check-tbd
d4mation Feb 13, 2026
e122fbc
Merge branch 'ENG-219/command-check' into ENG-219/command-check-tbd
d4mation Feb 13, 2026
dc6cace
ENG-219: Merge command-check and update imports to .ts
d4mation Feb 13, 2026
a43951d
ENG-219: Add --root flag test for TBD check
d4mation Feb 14, 2026
46c7118
ENG-219: Match TBD patterns from PHP implementation exactly
d4mation Feb 14, 2026
461dfa6
ENG-219: Add standalone quoted TBD cases to test fixture
d4mation Feb 14, 2026
79b7fd3
ENG-219: Add pattern-specific unit tests for TBD check
d4mation Feb 14, 2026
9e7eb18
ENG-219: Move TBD pattern tests into tbd.test.ts
d4mation Feb 14, 2026
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
11 changes: 7 additions & 4 deletions src/commands/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import chalk from 'chalk';
import { getConfig } from '../config.ts';

declare const BUILTIN_CHECK_SLUGS: string[];
import { executeTbdCheck } from './checks/tbd.ts';
import { runCommand } from '../utils/process.ts';
import * as output from '../utils/output.ts';
import type { CheckConfig, CheckResult } from '../types.ts';
Expand Down Expand Up @@ -97,19 +98,21 @@ export async function runChecks(options: {
* @since TBD
*
* @param {string} slug - The identifier for the built-in check.
* @param {CheckConfig} _checkConfig - The configuration for this check.
* @param {string} _cwd - The current working directory.
* @param {CheckConfig} checkConfig - The configuration for this check.
* @param {string} cwd - The current working directory.
* @param {ReturnType<typeof getConfig>} _config - The resolved pup configuration.
*
* @returns {Promise<CheckResult>} The result of the check.
*/
async function runBuiltinCheck(
slug: string,
_checkConfig: CheckConfig,
_cwd: string,
checkConfig: CheckConfig,
cwd: string,
_config: ReturnType<typeof getConfig>
): Promise<CheckResult> {
switch (slug) {
case 'tbd':
return executeTbdCheck(checkConfig, cwd);
default:
return { success: false, output: `Unknown built-in check: ${slug}` };
}
Expand Down
141 changes: 141 additions & 0 deletions src/commands/checks/tbd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'node:path';
import * as output from '../../utils/output.ts';
import type { CheckConfig, CheckResult } from '../../types.ts';

const DEFAULT_SKIP_DIRS = 'bin|build|vendor|node_modules|.git|.github|tests';
const DEFAULT_SKIP_FILES = '.min.css|.min.js|.map.js|.css|.png|.jpg|.jpeg|.svg|.gif|.ico';
const DEFAULT_DIRS = ['src'];

interface TbdMatch {
file: string;
line: number;
content: string;
}

/**
* Scans configured directories for TBD markers.
*
* @since TBD
*
* @param {CheckConfig} config - The check configuration containing directories and skip patterns.
* @param {string} workingDir - The working directory to scan relative to.
*
* @returns {Promise<CheckResult>} A CheckResult indicating success or failure with details about found TBDs.
*/
export async function executeTbdCheck(
config: CheckConfig,
workingDir: string
): Promise<CheckResult> {
const skipDirs = (config.skip_directories ?? DEFAULT_SKIP_DIRS).split('|');
const skipFiles = (config.skip_files ?? DEFAULT_SKIP_FILES).split('|');
const dirs = config.dirs ?? DEFAULT_DIRS;

output.section('Checking for TBDs...');

const matches: TbdMatch[] = [];

for (const dir of dirs) {
const dirPath = path.resolve(workingDir, dir);
if (!(await fs.pathExists(dirPath))) continue;
await scanDirectory(dirPath, workingDir, skipDirs, skipFiles, matches);
}

if (matches.length === 0) {
output.success('No TBDs found!');
output.log('');
output.log('');
output.success('Success! No TBDs found.');
return { success: true, output: '' };
}

// Group by file
const grouped = new Map<string, TbdMatch[]>();
for (const match of matches) {
const existing = grouped.get(match.file) ?? [];
existing.push(match);
grouped.set(match.file, existing);
}

for (const [file, fileMatches] of grouped) {
const relPath = path.relative(workingDir, file);
output.log(chalk.cyan(relPath));
for (const m of fileMatches) {
output.log(`${chalk.yellow(`${m.line}:`)} ${m.content.trim()}`);
}
output.log('');
}

output.log('');
output.error('TBDs have been found!');

return { success: false, output: '' };
}

/**
* Recursively scans a directory for files containing TBD markers.
*
* @since TBD
*
* @param {string} dir - The directory to scan.
* @param {string} workingDir - The working directory used for relative path calculations.
* @param {string[]} skipDirs - Array of directory names to skip during scanning.
* @param {string[]} skipFiles - Array of file extensions/patterns to skip during scanning.
* @param {TbdMatch[]} matches - Array to accumulate TBD matches found during the scan.
*
* @returns {Promise<void>}
*/
async function scanDirectory(
dir: string,
workingDir: string,
skipDirs: string[],
skipFiles: string[],
matches: TbdMatch[]
): Promise<void> {
const entries = await fs.readdir(dir, { withFileTypes: true });

for (const entry of entries) {
const fullPath = path.join(dir, entry.name);

if (entry.isDirectory()) {
if (skipDirs.some((skip) => entry.name === skip)) continue;
await scanDirectory(fullPath, workingDir, skipDirs, skipFiles, matches);
} else if (entry.isFile()) {
if (skipFiles.some((skip) => entry.name.endsWith(skip))) continue;
if (entry.name.startsWith('.pup-') || entry.name === '.puprc') continue;
await scanFile(fullPath, matches);
}
}
}

/**
* Scans a single file for lines containing TBD markers.
*
* @since TBD
*
* @param {string} filePath - The full path to the file to scan.
* @param {TbdMatch[]} matches - Array to accumulate TBD matches found in the file.
*
* @returns {Promise<void>}
*/
async function scanFile(filePath: string, matches: TbdMatch[]): Promise<void> {
const contents = await fs.readFile(filePath, 'utf-8');
const lines = contents.split('\n');

const tbdPatterns = [
/\*\s*@(since|deprecated|version)\s.*tbd/i,
/_deprecated_\w\(.*['"]tbd['"]/i,
/['"]tbd['"]/i,
];

for (let i = 0; i < lines.length; i++) {
if (tbdPatterns.some((pattern) => pattern.test(lines[i]))) {
matches.push({
file: filePath,
line: i + 1,
content: lines[i],
});
}
}
}
26 changes: 26 additions & 0 deletions tests/commands/check.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,32 @@ describe('check command', () => {
});
});

describe('check subcommands', () => {
afterEach(() => {
cleanupTempProjects();
});

it('should register built-in check:tbd even when not in .puprc', async () => {
const projectDir = createTempProject();
// Only version-conflict configured, no tbd
writePuprc(getPuprc({ checks: { 'version-conflict': {} } }), projectDir);

const result = await runPup('check:tbd', { cwd: projectDir });
expect(result.output).toContain('Checking for TBDs...');
expect(result.output).not.toContain("unknown command 'check:tbd'");
});

it('should run check:tbd without prefix', async () => {
const projectDir = createTempProject();
writePuprc(getPuprc(), projectDir);

const result = await runPup('check:tbd', { cwd: projectDir });
expect(result.exitCode).toBe(0);
expect(result.output).toContain('Checking for TBDs...');
expect(result.output).not.toContain('[tbd]');
});
});

describe('custom checks', () => {
afterEach(() => {
cleanupTempProjects();
Expand Down
Loading