// Evidence inventory, not a second requirements matrix. Static bindings are not acceptance. const fs = require('node:fs'); const path = require('node:path'); const ts = require('typescript'); const root = path.resolve(__dirname, '..'); const rows = []; const controls = []; function walk(directory) { for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { const file = path.join(directory, entry.name); if (entry.isDirectory()) walk(file); else if (entry.name.endsWith('.tsx')) { const source = fs.readFileSync(file, 'utf8'); const tree = ts.createSourceFile( file, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX, ); function visit(node) { const opening = ts.isJsxElement(node) ? node.openingElement : ts.isJsxSelfClosingElement(node) ? node : null; if (opening) { const tag = opening.tagName.getText(tree); const attributes = opening.attributes.properties; const names = attributes.map((a) => a.name?.getText(tree)); const base = { file: path.relative(root, file).replace(/\\/g, '/'), line: tree.getLineAndCharacterOfPosition( node.getStart(tree), ).line + 1, component: tag, }; if (tag === 'button' || tag === 'Button') { let form = null; for ( let parent = node.parent; parent; parent = parent.parent ) { if ( ts.isJsxElement(parent) && ['form', 'Form'].includes( parent.openingElement.tagName.getText(tree), ) ) { form = parent.openingElement; break; } } const formNames = form?.attributes.properties.map((a) => a.name?.getText(tree), ) ?? []; rows.push({ ...base, label: ts.isJsxElement(node) ? node.children .map((c) => c.getText(tree)) .join(' ') .replace(/\s+/g, ' ') .slice(0, 180) : '', binding: names.some((n) => ['onClick', 'onSelect'].includes(n), ) ? 'direct_handler' : form && form.tagName.getText(tree) === 'Form' ? 'inertia_form' : formNames.includes('onSubmit') ? 'form_handler' : form && (formNames.includes('action') || formNames.includes('method')) ? 'native_form_action' : names.includes('asChild') ? 'delegated_child' : 'context_or_handler_review', functional_demonstration: 'Not inferred from static binding; use per-journey QA evidence and visual walkthrough', }); } if ( tag === 'select' || /CountrySelect|ProvinceSelect|TerritoryFields|TerritoryLevel|TerritoryField|MapPanel|InvestmentMap|Geography/.test( tag, ) ) { controls.push({ ...base, attributes: attributes .map((a) => a.getText(tree)) .join(' ') .slice(0, 700), visual_verified: false, }); } } ts.forEachChild(node, visit); } visit(tree); } } } walk(path.join(root, 'resources/js/pages')); walk(path.join(root, 'resources/js/components')); const file = path.join( root, 'storage/app/private/investmoz-v2/portal-audit/consumer-actions-lote2.json', ); fs.writeFileSync( file, JSON.stringify( { captured_at: new Date().toISOString(), scope: 'All TSX pages/components; inventory of source controls and action bindings, not rendered DOM or functional completion', buttons: rows, selectors_and_maps: controls, counts: { buttons: rows.length, selectors_and_maps: controls.length, binding_review: rows.filter( (r) => r.binding === 'context_or_handler_review', ).length, }, visual_verified: false, }, null, 2, ), ); console.log( JSON.stringify({ buttons: rows.length, selectors_and_maps: controls.length, review: rows .filter((r) => r.binding === 'context_or_handler_review') .map(({ file, line, label }) => ({ file, line, label })), }), );