From c4a2bd0e7b29abc201d40b7cdae7815a508f8681 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 26 Jan 2026 13:01:53 -0400 Subject: infra: collect things with wildcard exports Removes checking for duplicate class names. I think that's meant to be an error in ES6 modules (i.e. because two exports would be sharing the same name), but Node.js seemingly is fine with it, so just be careful for now. --- src/data/things/index.js | 295 ++++------------------------------------------- 1 file changed, 20 insertions(+), 275 deletions(-) (limited to 'src/data/things/index.js') diff --git a/src/data/things/index.js b/src/data/things/index.js index 35cd8cf2..bf3df9a7 100644 --- a/src/data/things/index.js +++ b/src/data/things/index.js @@ -1,275 +1,20 @@ -import * as path from 'node:path'; -import {fileURLToPath} from 'node:url'; - -import {openAggregate, showAggregate} from '#aggregate'; -import CacheableObject from '#cacheable-object'; -import {logError} from '#cli'; -import {compositeFrom} from '#composite'; -import * as serialize from '#serialize'; -import {empty} from '#sugar'; -import Thing from '#thing'; - -import * as additionalFileClasses from './additional-file.js'; -import * as additionalNameClasses from './additional-name.js'; -import * as albumClasses from './album.js'; -import * as artTagClasses from './art-tag.js'; -import * as artistClasses from './artist.js'; -import * as artworkClasses from './artwork.js'; -import * as contentClasses from './content.js'; -import * as contributionClasses from './contribution.js'; -import * as flashClasses from './flash.js'; -import * as groupClasses from './group.js'; -import * as homepageLayoutClasses from './homepage-layout.js'; -import * as languageClasses from './language.js'; -import * as musicVideoClasses from './music-video.js'; -import * as newsEntryClasses from './news-entry.js'; -import * as sortingRuleClasses from './sorting-rule.js'; -import * as staticPageClasses from './static-page.js'; -import * as trackClasses from './track.js'; -import * as wikiInfoClasses from './wiki-info.js'; - -const allClassLists = { - 'additional-file.js': additionalFileClasses, - 'additional-name.js': additionalNameClasses, - 'album.js': albumClasses, - 'art-tag.js': artTagClasses, - 'artist.js': artistClasses, - 'artwork.js': artworkClasses, - 'content.js': contentClasses, - 'contribution.js': contributionClasses, - 'flash.js': flashClasses, - 'group.js': groupClasses, - 'homepage-layout.js': homepageLayoutClasses, - 'language.js': languageClasses, - 'music-video.js': musicVideoClasses, - 'news-entry.js': newsEntryClasses, - 'sorting-rule.js': sortingRuleClasses, - 'static-page.js': staticPageClasses, - 'track.js': trackClasses, - 'wiki-info.js': wikiInfoClasses, -}; - -let allClasses = Object.create(null); - -// src/data/things/index.js -> src/ -const __dirname = path.dirname( - path.resolve( - fileURLToPath(import.meta.url), - '../..')); - -function niceShowAggregate(error, ...opts) { - showAggregate(error, { - pathToFileURL: (f) => path.relative(__dirname, fileURLToPath(f)), - showClasses: false, - ...opts, - }); -} - -function errorDuplicateClassNames() { - const locationDict = Object.create(null); - - for (const [location, classes] of Object.entries(allClassLists)) { - for (const className of Object.keys(classes)) { - if (className in locationDict) { - locationDict[className].push(location); - } else { - locationDict[className] = [location]; - } - } - } - - let success = true; - - for (const [className, locations] of Object.entries(locationDict)) { - if (locations.length === 1) { - continue; - } - - logError`Thing class name ${`"${className}"`} is defined more than once: ${locations.join(', ')}`; - success = false; - } - - return success; -} - -function flattenClassLists() { - let remaining = []; - for (const classes of Object.values(allClassLists)) { - for (const constructor of Object.values(classes)) { - if (typeof constructor !== 'function') continue; - if (!(constructor.prototype instanceof Thing)) continue; - remaining.push(constructor); - } - } - - let sorted = []; - while (true) { - if (sorted[0]) { - const superclass = Object.getPrototypeOf(sorted[0]); - if (superclass !== Thing) { - if (sorted.includes(superclass)) { - sorted.unshift(...sorted.splice(sorted.indexOf(superclass), 1)); - } else { - sorted.unshift(superclass); - } - continue; - } - } - - if (!empty(remaining)) { - sorted.unshift(remaining.shift()); - } else { - break; - } - } - - for (const constructor of sorted) { - allClasses[constructor.name] = constructor; - } -} - -function descriptorAggregateHelper({ - showFailedClasses, - message, - op, -}) { - const failureSymbol = Symbol(); - const aggregate = openAggregate({ - message, - returnOnFail: failureSymbol, - }); - - const failedClasses = []; - - for (const [name, constructor] of Object.entries(allClasses)) { - const result = aggregate.call(op, constructor); - - if (result === failureSymbol) { - failedClasses.push(name); - } - } - - try { - aggregate.close(); - return true; - } catch (error) { - niceShowAggregate(error); - showFailedClasses(failedClasses); - - /* - if (error.errors) { - for (const sub of error.errors) { - console.error(sub); - } - } - */ - - return false; - } -} - -function evaluatePropertyDescriptors() { - const opts = {...allClasses}; - - return descriptorAggregateHelper({ - message: `Errors evaluating Thing class property descriptors`, - - op(constructor) { - if (!constructor[Thing.getPropertyDescriptors]) { - throw new Error(`Missing [Thing.getPropertyDescriptors] function`); - } - - const results = constructor[Thing.getPropertyDescriptors](opts); - - for (const [key, value] of Object.entries(results)) { - if (Array.isArray(value)) { - results[key] = compositeFrom({ - annotation: `${constructor.name}.${key}`, - compose: false, - steps: value, - }); - } else if (value.toResolvedComposition) { - results[key] = compositeFrom(value.toResolvedComposition()); - } - } - - constructor[CacheableObject.propertyDescriptors] = - Object.create(constructor[CacheableObject.propertyDescriptors] ?? null); - - Object.assign(constructor[CacheableObject.propertyDescriptors], results); - }, - - showFailedClasses(failedClasses) { - logError`Failed to evaluate property descriptors for classes: ${failedClasses.join(', ')}`; - }, - }); -} - -function evaluateSerializeDescriptors() { - const opts = {...allClasses, serialize}; - - return descriptorAggregateHelper({ - message: `Errors evaluating Thing class serialize descriptors`, - - op(constructor) { - if (!constructor[Thing.getSerializeDescriptors]) { - return; - } - - constructor[serialize.serializeDescriptors] = - constructor[Thing.getSerializeDescriptors](opts); - }, - - showFailedClasses(failedClasses) { - logError`Failed to evaluate serialize descriptors for classes: ${failedClasses.join(', ')}`; - }, - }); -} - -function finalizeYamlDocumentSpecs() { - return descriptorAggregateHelper({ - message: `Errors finalizing Thing YAML document specs`, - - op(constructor) { - const superclass = Object.getPrototypeOf(constructor); - if ( - constructor[Thing.yamlDocumentSpec] && - superclass[Thing.yamlDocumentSpec] - ) { - constructor[Thing.yamlDocumentSpec] = - Thing.extendDocumentSpec(superclass, constructor[Thing.yamlDocumentSpec]); - } - }, - - showFailedClasses(failedClasses) { - logError`Failed to finalize YAML document specs for classes: ${failedClasses.join(', ')}`; - }, - }); -} - -function finalizeCacheableObjectPrototypes() { - return descriptorAggregateHelper({ - message: `Errors finalizing Thing class prototypes`, - - op(constructor) { - constructor.finalizeCacheableObjectPrototype(); - }, - - showFailedClasses(failedClasses) { - logError`Failed to finalize cacheable object prototypes for classes: ${failedClasses.join(', ')}`; - }, - }); -} - -if (!errorDuplicateClassNames()) process.exit(1); - -flattenClassLists(); - -if (!evaluatePropertyDescriptors()) process.exit(1); -if (!evaluateSerializeDescriptors()) process.exit(1); -if (!finalizeYamlDocumentSpecs()) process.exit(1); -if (!finalizeCacheableObjectPrototypes()) process.exit(1); - -Object.assign(allClasses, {Thing}); - -export default allClasses; +// Not actually the entry point for #things - that's init.js in this folder. + +export * from './additional-file.js'; +export * from './additional-name.js'; +export * from './album.js'; +export * from './art-tag.js'; +export * from './artist.js'; +export * from './artwork.js'; +export * from './content.js'; +export * from './contribution.js'; +export * from './flash.js'; +export * from './group.js'; +export * from './homepage-layout.js'; +export * from './language.js'; +export * from './music-video.js'; +export * from './news-entry.js'; +export * from './sorting-rule.js'; +export * from './static-page.js'; +export * from './track.js'; +export * from './wiki-info.js'; -- cgit 1.3.0-6-gf8a5