fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at feat/use-query-options 328 lines 9.1 kB view raw
1import { symbolBrand } from '../brands'; 2import type { ISymbolMeta } from '../extensions'; 3import type { File } from '../files/file'; 4import { log } from '../log'; 5import type { INode } from '../nodes/node'; 6import type { BindingKind, ISymbolIn, SymbolKind } from './types'; 7 8export class Symbol<Node extends INode = INode> { 9 /** 10 * Canonical symbol this stub resolves to, if any. 11 * 12 * Stubs created during DSL construction may later be associated 13 * with a fully registered symbol. Once set, all property lookups 14 * should defer to the canonical symbol. 15 */ 16 private _canonical?: Symbol; 17 /** 18 * True if this symbol is exported from its defining file. 19 * 20 * @default false 21 */ 22 private _exported: boolean; 23 /** 24 * External module name if this symbol is imported from a module not managed 25 * by the project (e.g., "zod", "lodash"). 26 * 27 * @default undefined 28 */ 29 private _external?: string; 30 /** 31 * The file this symbol is ultimately emitted into. 32 * 33 * Only top-level symbols have an assigned file. 34 */ 35 private _file?: File; 36 /** 37 * The alias-resolved, conflict-free emitted name. 38 */ 39 private _finalName?: string; 40 /** 41 * Custom strategy to determine from which file path(s) this symbol is re-exported. 42 * 43 * @returns The file path(s) that re-export this symbol, or undefined if none. 44 */ 45 private _getExportFromFilePath?: (symbol: Symbol) => ReadonlyArray<string> | undefined; 46 /** 47 * Custom strategy to determine file output path. 48 * 49 * @returns The file path to output the symbol to, or undefined to fallback to default behavior. 50 */ 51 private _getFilePath?: (symbol: Symbol) => string | undefined; 52 /** 53 * How this symbol should be imported (namespace/default/named). 54 * 55 * @default 'named' 56 */ 57 private _importKind: BindingKind; 58 /** 59 * Kind of symbol (class, type, alias, etc.). 60 * 61 * @default 'var' 62 */ 63 private _kind: SymbolKind; 64 /** 65 * Arbitrary user metadata. 66 * 67 * @default undefined 68 */ 69 private _meta?: ISymbolMeta; 70 /** 71 * Intended user-facing name before conflict resolution. 72 * 73 * @example "UserModel" 74 */ 75 private _name: string; 76 /** 77 * Node that defines this symbol. 78 */ 79 private _node?: Node; 80 81 /** Brand used for identifying symbols. */ 82 readonly '~brand' = symbolBrand; 83 /** Globally unique, stable symbol ID. */ 84 readonly id: number; 85 86 constructor(input: ISymbolIn, id: number) { 87 this._exported = input.exported ?? false; 88 this._external = input.external; 89 this._getExportFromFilePath = input.getExportFromFilePath; 90 this._getFilePath = input.getFilePath; 91 this.id = id; 92 this._importKind = input.importKind ?? 'named'; 93 this._kind = input.kind ?? 'var'; 94 this._meta = input.meta; 95 this._name = input.name; 96 } 97 98 /** 99 * Returns the canonical symbol for this instance. 100 * 101 * If this symbol was created as a stub, this getter returns 102 * the fully registered canonical symbol. Otherwise, it returns 103 * the symbol itself. 104 */ 105 get canonical(): Symbol { 106 return this._canonical ?? this; 107 } 108 109 /** 110 * Indicates whether this symbol is exported from its defining file. 111 */ 112 get exported(): boolean { 113 return this.canonical._exported; 114 } 115 116 /** 117 * External module from which this symbol originates, if any. 118 */ 119 get external(): string | undefined { 120 return this.canonical._external; 121 } 122 123 /** 124 * Read‑only accessor for the assigned output file. 125 * 126 * Only top-level symbols have an assigned file. 127 */ 128 get file(): File | undefined { 129 return this.canonical._file; 130 } 131 132 /** 133 * Read‑only accessor for the resolved final emitted name. 134 */ 135 get finalName(): string { 136 if (!this.canonical._finalName) { 137 const message = `Symbol finalName has not been resolved yet for ${this.canonical.toString()}`; 138 log.debug(message, 'symbol'); 139 throw new Error(message); 140 } 141 return this.canonical._finalName; 142 } 143 144 /** 145 * Custom re-export file path resolver, if provided. 146 */ 147 get getExportFromFilePath(): ((symbol: Symbol) => ReadonlyArray<string> | undefined) | undefined { 148 return this.canonical._getExportFromFilePath; 149 } 150 151 /** 152 * Custom file path resolver, if provided. 153 */ 154 get getFilePath(): ((symbol: Symbol) => string | undefined) | undefined { 155 return this.canonical._getFilePath; 156 } 157 158 /** 159 * How this symbol should be imported (named/default/namespace). 160 */ 161 get importKind(): BindingKind { 162 return this.canonical._importKind; 163 } 164 165 /** 166 * Indicates whether this is a canonical symbol (not a stub). 167 */ 168 get isCanonical(): boolean { 169 return !this._canonical || this._canonical === this; 170 } 171 172 /** 173 * The symbol's kind (class, type, alias, variable, etc.). 174 */ 175 get kind(): SymbolKind { 176 return this.canonical._kind; 177 } 178 179 /** 180 * Arbitrary user‑provided metadata associated with this symbol. 181 */ 182 get meta(): ISymbolMeta | undefined { 183 return this.canonical._meta; 184 } 185 186 /** 187 * User-intended name before aliasing or conflict resolution. 188 */ 189 get name(): string { 190 return this.canonical._name; 191 } 192 193 /** 194 * Read‑only accessor for the defining node. 195 */ 196 get node(): Node | undefined { 197 return this.canonical._node as Node | undefined; 198 } 199 200 /** 201 * Marks this symbol as a stub and assigns its canonical symbol. 202 * 203 * After calling this, all semantic queries (name, kind, file, 204 * meta, etc.) should reflect the canonical symbol's values. 205 * 206 * @param symbol — The canonical symbol this stub should resolve to. 207 */ 208 setCanonical(symbol: Symbol): void { 209 this._canonical = symbol; 210 } 211 212 /** 213 * Marks the symbol as exported from its file. 214 * 215 * @param exported — Whether the symbol is exported. 216 */ 217 setExported(exported: boolean): void { 218 this.assertCanonical(); 219 this._exported = exported; 220 } 221 222 /** 223 * Assigns the output file this symbol will be emitted into. 224 * 225 * This may only be set once. 226 */ 227 setFile(file: File): void { 228 this.assertCanonical(); 229 if (this._file && this._file !== file) { 230 const message = `Symbol ${this.canonical.toString()} is already assigned to a different file.`; 231 log.debug(message, 'symbol'); 232 throw new Error(message); 233 } 234 this._file = file; 235 } 236 237 /** 238 * Assigns the conflict‑resolved final local name for this symbol. 239 * 240 * This may only be set once. 241 */ 242 setFinalName(name: string): void { 243 this.assertCanonical(); 244 if (this._finalName && this._finalName !== name) { 245 const message = `Symbol finalName has already been resolved for ${this.canonical.toString()}.`; 246 log.debug(message, 'symbol'); 247 throw new Error(message); 248 } 249 this._finalName = name; 250 } 251 252 /** 253 * Sets how this symbol should be imported. 254 * 255 * @param kind — The import strategy (named/default/namespace). 256 */ 257 setImportKind(kind: BindingKind): void { 258 this.assertCanonical(); 259 this._importKind = kind; 260 } 261 262 /** 263 * Sets the symbol's kind (class, type, alias, variable, etc.). 264 * 265 * @param kind — The new symbol kind. 266 */ 267 setKind(kind: SymbolKind): void { 268 this.assertCanonical(); 269 this._kind = kind; 270 } 271 272 /** 273 * Updates the intended user‑facing name for this symbol. 274 * 275 * @param name — The new name. 276 */ 277 setName(name: string): void { 278 this.assertCanonical(); 279 this._name = name; 280 } 281 282 /** 283 * Binds the node that defines this symbol. 284 * 285 * This may only be set once. 286 */ 287 setNode(node: Node): void { 288 this.assertCanonical(); 289 if (this._node && this._node !== node) { 290 const message = `Symbol ${this.canonical.toString()} is already bound to a different node.`; 291 log.debug(message, 'symbol'); 292 // TODO: symbol <> node relationship needs to be refactor to 1:many 293 // disabled in the meantime or it would throw 294 // throw new Error(message); 295 } 296 this._node = node; 297 node.symbol = this; 298 } 299 300 /** 301 * Returns a debug‑friendly string representation identifying the symbol. 302 */ 303 toString(): string { 304 const canonical = this.canonical; 305 if (canonical._finalName && canonical._finalName !== canonical._name) { 306 return `[Symbol ${canonical._name}${canonical._finalName}#${canonical.id}]`; 307 } 308 return `[Symbol ${canonical._name || canonical._meta !== undefined ? JSON.stringify(canonical._meta) : '<unknown>'}#${canonical.id}]`; 309 } 310 311 /** 312 * Ensures this symbol is canonical before allowing mutation. 313 * 314 * A symbol that has been marked as a stub (i.e., its `_canonical` points 315 * to a different symbol) may not be mutated. This guard throws an error 316 * if any setter attempts to modify a stub, preventing accidental writes 317 * to non‑canonical instances. 318 * 319 * @throws {Error} If the symbol is a stub and is being mutated. 320 */ 321 private assertCanonical(): void { 322 if (this._canonical && this._canonical !== this) { 323 const message = `Illegal mutation of stub symbol ${this.toString()} → canonical: ${this._canonical.toString()}`; 324 log.debug(message, 'symbol'); 325 throw new Error(message); 326 } 327 } 328}