import { StringFormat } from "./string-format"; /** * A table for unique symbols (for any DescriptorProto, EnumDescriptorProto * or ServiceDescriptorProto) in files (GeneratedFile). */ export class SymbolTable { constructor(clashResolver) { this.entries = []; this.clashResolveMaxTries = 100; this.hasNameInFile = (name, file) => this.entries.some(e => e.file === file && e.name === name); this.clashResolver = clashResolver !== null && clashResolver !== void 0 ? clashResolver : SymbolTable.defaultClashResolver; } /** * Register a symbol in the given file for the given descriptor. * * If the name is already taken in the file, an alternative name * is automatically generated by appending '$' and a running * number to the requested name. You can change the behaviour by * providing your own `clashResolver`. * * Only one symbol per kind can be registered for a descriptor. * * If you want to generate an interface *and* a class for a * message, use a different `kind` for each. * * Returns the actual name registered. */ register(requestedName, descriptor, file, kind = 'default') { // Only one symbol per kind can be registered for a descriptor. if (this.has(descriptor, kind)) { let { file, name } = this.get(descriptor, kind); let msg = `Cannot register name "${requestedName}" of kind "${kind}" for ${StringFormat.formatName(descriptor)}. ` + `The descriptor is already registered in file "${file.getFilename()}" with name "${name}". ` + `Use a different 'kind' to register multiple symbols for a descriptor.`; throw new Error(msg); } // find a free name within the file let name = requestedName; let count = 0; while (this.hasNameInFile(name, file) && count < this.clashResolveMaxTries) { name = this.clashResolver(descriptor, file, requestedName, kind, ++count, name); } if (this.hasNameInFile(name, file)) { let msg = `Failed to register name "${requestedName}" for ${StringFormat.formatName(descriptor)}. ` + `Gave up finding alternative name after ${this.clashResolveMaxTries} tries. ` + `There is something wrong with the clash resolver.`; throw new Error(msg); } // add the entry and return name this.entries.push({ file, descriptor, kind, name }); return name; } /** * Find a symbol (of the given kind) for the given descriptor. * Return `undefined` if not found. */ find(descriptor, kind = 'default') { return this.entries.find(e => e.descriptor === descriptor && e.kind === kind); } /** * Find a symbol (of the given kind) for the given descriptor. * Raises error if not found. */ get(descriptor, kind = 'default') { const found = this.find(descriptor, kind); if (!found) { let files = this.entries.map(e => e.file) .filter((value, index, array) => array.indexOf(value) === index); let msg = `Failed to find name for ${StringFormat.formatName(descriptor)} of kind "${kind}". ` + `Searched in ${files.length} files.`; throw new Error(msg); } return found; } /** * Is a name (of the given kind) registered for the the given descriptor? */ has(descriptor, kind = 'default') { return !!this.find(descriptor, kind); } list(file, kind) { let matches = this.entries.filter(e => e.file === file); if (kind !== undefined) { matches = matches.filter(e => e.kind === kind); } return matches; } static defaultClashResolver(descriptor, file, requestedName, kind, tryCount) { let n = requestedName; n = n.endsWith('$') ? n.substring(1) : n; return n + '$' + tryCount; } }