Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | 1x 44x 44x 40x 54x 54x 10x 54x 54x 25x 40x 7x 7x 7x 7x 7x 7x 7x 7x 7x 7x 3x 3x 3x 3x 3x 3x 1x 1x 1x 2x 2x 2x 2x 2x 2x 2x 3x 1x 1x | #!/usr/bin/env node
/**
* DTCG Compliance Validator
*
* Scans token source JSON files and fails if any token uses non-DTCG property
* names where a DTCG equivalent exists (e.g. "deprecated" instead of "$deprecated").
*
* Usage:
* node src/validators/dtcg-compliance.js # Summary only
* node src/validators/dtcg-compliance.js --verbose # Detailed issues with suggestions
* node src/validators/dtcg-compliance.js --report # Full table of all scanned files
*/
import fs from 'fs';
import path from 'node:path';
import { glob } from 'glob';
import { parseValidatorArgs } from './validator-utils.js';
const DTCG_VIOLATIONS = new Map([
['deprecated', '$deprecated'],
['value', '$value'],
['type', '$type'],
['description', '$description'],
]);
/**
* Recursively walk a parsed token tree looking for non-DTCG keys
* where a $-prefixed equivalent exists.
*/
export function findViolations(obj, filePath, currentPath = []) {
const violations = [];
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return violations;
for (const [key, value] of Object.entries(obj)) {
const dtcgEquivalent = DTCG_VIOLATIONS.get(key);
if (dtcgEquivalent && !obj[dtcgEquivalent]) {
violations.push({
file: filePath,
path: [...currentPath, key].join('.'),
key,
expected: dtcgEquivalent,
});
}
const isValuePayload = key === '$value' || key === 'original';
if (typeof value === 'object' && value !== null && !Array.isArray(value) && !isValuePayload) {
violations.push(...findViolations(value, filePath, [...currentPath, key]));
}
}
return violations;
}
/**
* Scan all token JSON files for DTCG compliance violations.
* @param {string} tokensDir - Absolute path to the tokens directory
* @returns {{ violations: Array, filesScanned: number, fileResults: Array }}
*/
export function validateDtcgCompliance(tokensDir) {
const files = glob.sync('**/*.json', { cwd: tokensDir, absolute: true });
const allViolations = [];
const fileResults = [];
for (const file of files) {
const content = JSON.parse(fs.readFileSync(file, 'utf8'));
const relativePath = path.relative(tokensDir, file);
const fileViolations = findViolations(content, relativePath);
allViolations.push(...fileViolations);
fileResults.push({ file: relativePath, violationCount: fileViolations.length });
}
return { violations: allViolations, filesScanned: files.length, fileResults };
}
/**
* Run the validator and print results.
* @param {string} tokensDir - Absolute path to the tokens directory
* @param {{ isVerbose?: boolean, isReport?: boolean, exitFn?: Function }} options
* @returns {{ violations: Array, filesScanned: number }}
*/
export function run(tokensDir, { isVerbose = false, isReport = false, exitFn = process.exit } = {}) {
console.log(`\nDTCG Compliance Validation`);
console.log('─'.repeat(60));
const { violations, filesScanned, fileResults } = validateDtcgCompliance(tokensDir);
console.log(`Scanned ${filesScanned} token files.\n`);
Iif (isReport) {
for (const { file, violationCount } of fileResults) {
const status = violationCount === 0 ? '✅' : `❌ ${violationCount} violation(s)`;
console.log(` ${file} — ${status}`);
}
console.log('');
}
if (violations.length === 0) {
console.log('─'.repeat(60));
console.log('✅ All tokens use DTCG-compliant property names.\n');
exitFn(0);
} else {
console.log(`❌ Found ${violations.length} non-DTCG property name(s):\n`);
for (const v of violations) {
console.log(` ${v.file} → "${v.key}" at ${v.path}`);
if (isVerbose) console.log(` Use "${v.expected}" instead.\n`);
}
console.log('─'.repeat(60));
console.log(`❌ Validation failed: ${violations.length} violation(s) found.\n`);
exitFn(1);
}
return { violations, filesScanned };
}
// CLI entry point
const isCli = import.meta.url === `file://${process.argv[1]}`;
Iif (isCli) {
const { isVerbose, isReport } = parseValidatorArgs();
const tokensDir = path.resolve(process.cwd(), 'src/tokens');
run(tokensDir, { isVerbose, isReport });
}
|