#!/usr/bin/env node /** * Audit: extract all HS sources from behavioral tests and check parse/compile. * Uses child_process.execSync with timeout to handle hangs. */ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const PROJECT = path.resolve(__dirname, '..'); // Extract HS sources from test file with their suite/test names const testSx = fs.readFileSync(path.join(PROJECT, 'spec/tests/test-hyperscript-behavioral.sx'), 'utf8'); // Extract suites and their test counts from comments const suitePattern = /;; ── (\S+) \((\d+) tests\)/g; const suites = []; let m; while ((m = suitePattern.exec(testSx)) !== null) { suites.push({ name: m[1], count: parseInt(m[2]) }); } console.log('Test suites (831 total):'); let grandTotal = 0; for (const s of suites) { grandTotal += s.count; console.log(` ${s.name}: ${s.count}`); } console.log(` TOTAL: ${grandTotal}`); // Categorize tests by type: DOM-action tests vs eval-only tests vs stub tests const lines = testSx.split('\n'); let currentSuite = ''; let inDeftest = false; let testName = ''; let testBody = ''; let depth = 0; const testCategories = {}; for (const line of lines) { const suiteMatch = line.match(/\(defsuite\s+"([^"]+)"/); if (suiteMatch) { currentSuite = suiteMatch[1]; continue; } const testMatch = line.match(/\(deftest\s+"([^"]+)"/); if (testMatch) { inDeftest = true; testName = testMatch[1]; testBody = line; depth = 1; continue; } if (inDeftest) { testBody += '\n' + line; depth += (line.match(/\(/g) || []).length - (line.match(/\)/g) || []).length; if (depth <= 0) { // Categorize this test if (!testCategories[currentSuite]) testCategories[currentSuite] = { domAction: 0, evalOnly: 0, stub: 0, notImpl: 0, names: [] }; if (testBody.includes('NOT IMPLEMENTED') || testBody.includes('not-implemented')) { testCategories[currentSuite].stub++; } else if (testBody.includes('dom-dispatch') || testBody.includes('dom-set-inner-html') && testBody.includes('hs-activate!')) { testCategories[currentSuite].domAction++; testCategories[currentSuite].names.push(testName); } else if (testBody.includes('eval-hs') || testBody.includes('hs-eval')) { testCategories[currentSuite].evalOnly++; } else { testCategories[currentSuite].domAction++; testCategories[currentSuite].names.push(testName); } inDeftest = false; testBody = ''; } } } // Count unique HS sources const attrPattern = /dom-set-attr\s+\S+\s+"_"\s+"([^"]+)"/g; const hsSources = new Set(); while ((m = attrPattern.exec(testSx)) !== null) { hsSources.add(m[1].replace(/\\"/g, '"')); } console.log(`\n${hsSources.size} unique HS sources across ${grandTotal} tests`); // Analyze HS source complexity const features = { 'on click': 0, 'on load': 0, 'on keyup': 0, 'on mouseenter': 0, 'on mutation': 0, 'on intersect': 0, 'on every': 0, 'add .': 0, 'remove .': 0, 'toggle .': 0, 'set ': 0, 'put ': 0, 'if ': 0, 'repeat': 0, 'wait ': 0, 'send ': 0, 'take ': 0, 'transition': 0, 'hide': 0, 'show': 0, 'log': 0, 'call ': 0, 'fetch ': 0, 'tell ': 0, 'halt': 0, 'morph': 0, 'go ': 0, 'append': 0, 'settle': 0, 'def ': 0, 'increment': 0, 'decrement': 0, 'bind ': 0, 'closest': 0, 'in ': 0, 'as ': 0, 'init': 0, 'every ': 0, 'measure': 0, }; for (const src of hsSources) { for (const feat of Object.keys(features)) { if (src.includes(feat)) features[feat]++; } } console.log('\nFeature usage in test sources:'); for (const [feat, count] of Object.entries(features).sort((a,b) => b[1] - a[1])) { if (count > 0) console.log(` ${feat.trim()}: ${count}`); } // Summarize categories console.log('\nPer-suite breakdown:'); let totalDom = 0, totalEval = 0, totalStub = 0; for (const [suite, cat] of Object.entries(testCategories).sort((a,b) => (b[1].domAction+b[1].evalOnly) - (a[1].domAction+a[1].evalOnly))) { const total = cat.domAction + cat.evalOnly + cat.stub; totalDom += cat.domAction; totalEval += cat.evalOnly; totalStub += cat.stub; console.log(` ${suite}: ${total} tests (${cat.domAction} DOM, ${cat.evalOnly} eval, ${cat.stub} stub)`); } console.log(`\nTotals: ${totalDom} DOM-action, ${totalEval} eval-only, ${totalStub} stubs`);