All files BaseSymbol.ts

91.89% Statements 238/259
73.46% Branches 36/49
88.23% Functions 15/17
91.89% Lines 238/259

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 2601x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 76924x 76924x 1x 1x 22169x 22169x 1x 1x 28x     28x 28x 28x 1x 1x 1x 1x 1x 3x     3x 3x     3x 3x 3x 1x 1x 1x 1x 1x 3x 3x 1x 1x 24x 24x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 4x 2x 2x 2x 2x       1x 1x 1x 1x 1x 96336x 22616x 22616x 73720x 73720x 96336x 95779x 73720x 73720x 22059x 22059x       1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 5x 5x 1x 1x 4x 4x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 115754x 115754x 1x 1x 1x 1x 1x 96335x 96335x 96335x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 1x 1x 1x 1x       1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 12x     12x 12x 12x 12x 34x 34x 34x 34x 34x 12x 12x 22x 22x 12x 12x 12x 1x 1x 1x 1x 1x 1x 1x 1x 1x 192118x 192118x 1x  
/*
 * This file is released under the MIT license.
 * Copyright (c) 2023, Mike Lischke
 *
 * See LICENSE file for more info.
 */
 
import { ParseTree } from "antlr4ts/tree/ParseTree";
 
import { MemberVisibility, Modifier, SymbolConstructor } from "./types";
 
import { type IScopedSymbol } from "./ScopedSymbol";
import { type ISymbolTable } from "./SymbolTable";
 
export interface IBaseSymbol {
    name: string;
    context?: ParseTree;
    modifiers: Set<Modifier>;
    visibility: MemberVisibility;
    parent?: IScopedSymbol;
    firstSibling?: IBaseSymbol;
    previousSibling?: IBaseSymbol;
    nextSibling?: IBaseSymbol;
    lastSibling?: IBaseSymbol;
    next?: IBaseSymbol;
    root?: IBaseSymbol;
    symbolTable?: ISymbolTable;
    symbolPath: IBaseSymbol[];
    setParent(parent?: IScopedSymbol): void;
    removeFromParent(): void;
    resolve(name: string, localOnly?: boolean): Promise<IBaseSymbol | undefined>;
    resolveSync(name: string, localOnly?: boolean): IBaseSymbol | undefined;
    getParentOfType<T extends BaseSymbol>(t: SymbolConstructor<T>): T | undefined;
}
 
/**
 * The root of the symbol table class hierarchy: a symbol can be any manageable entity (like a block), not only
 * things like variables or classes.
 * We are using a class hierarchy here, instead of an enum or similar, to allow for easy extension and certain
 * symbols can so provide additional APIs for simpler access to their sub elements, if needed.
 */
export class BaseSymbol implements IBaseSymbol {
    /** The name of the symbol or empty if anonymous. */
    public name;
 
    /** Reference to the parse tree which contains this symbol. */
    public context?: ParseTree;
 
    public readonly modifiers = new Set<Modifier>();
    public visibility = MemberVisibility.Unknown;
 
    #parent?: IScopedSymbol;
 
    public constructor(name = "") {
        this.name = name;
    }
 
    public get parent(): IScopedSymbol | undefined {
        return this.#parent;
    }
 
    public get firstSibling(): BaseSymbol | undefined {
        if (!this.#parent) {
            return undefined;
        }
 
        return this.#parent?.firstChild;
    }
 
    /**
     * @returns the symbol before this symbol in its scope.
     */
    public get previousSibling(): BaseSymbol | undefined {
        if (!this.#parent) {
            return undefined;
        }
 
        if (!this.#parent) {
            return this;
        }
 
        return this.#parent.previousSiblingOf(this);
    }
 
    /**
     * @returns the symbol following this symbol in its scope.
     */
    public get nextSibling(): BaseSymbol | undefined {
        return this.#parent?.nextSiblingOf(this);
    }
 
    public get lastSibling(): BaseSymbol | undefined {
        return this.#parent?.lastChild;
    }
 
    /**
     * @returns the next symbol in definition order, regardless of the scope.
     */
    public get next(): BaseSymbol | undefined {
        return this.#parent?.nextOf(this);
    }
 
    /**
     * @returns the outermost entity (below the symbol table) that holds us.
     */
    public get root(): IBaseSymbol | undefined {
        let run = this.#parent;
        while (run) {
            if (!run.parent || this.isSymbolTable(run.parent)) {
                return run;
            }
            run = run.parent;
        }

        return run;
    }
 
    /**
     * @returns the symbol table we belong too or undefined if we are not yet assigned.
     */
    public get symbolTable(): ISymbolTable | undefined {
        if (this.isSymbolTable(this)) {
            return this;
        }
 
        let run = this.#parent;
        while (run) {
            if (this.isSymbolTable(run)) {
                return run;
            }
            run = run.parent;
        }

        return undefined;
    }
 
    /**
     * @returns the list of symbols from this one up to root.
     */
    public get symbolPath(): IBaseSymbol[] {
        const result: IBaseSymbol[] = [];
 
        // eslint-disable-next-line @typescript-eslint/no-this-alias
        let run: IBaseSymbol = this;
        while (run) {
            result.push(run);
            if (!run.parent) {
                break;
            }
            run = run.parent;
        }
 
        return result;
    }
 
    /**
     * This is rather an internal method and should rarely be used by external code.
     *
     * @param parent The new parent to use.
     */
    public setParent(parent?: IScopedSymbol): void {
        this.#parent = parent;
    }
 
    /**
     * Remove this symbol from its parent scope.
     */
    public removeFromParent(): void {
        this.#parent?.removeSymbol(this);
        this.#parent = undefined;
    }
 
    /**
     * Asynchronously looks up a symbol with a given name, in a bottom-up manner.
     *
     * @param name The name of the symbol to find.
     * @param localOnly If true only child symbols are returned, otherwise also symbols from the parent of this symbol
     *                  (recursively).
     *
     * @returns A promise resolving to the first symbol with a given name, in the order of appearance in this scope
     *          or any of the parent scopes (conditionally).
     */
    public async resolve(name: string, localOnly = false): Promise<BaseSymbol | undefined> {
        return this.#parent?.resolve(name, localOnly);
    }
 
    /**
     * Synchronously looks up a symbol with a given name, in a bottom-up manner.
     *
     * @param name The name of the symbol to find.
     * @param localOnly If true only child symbols are returned, otherwise also symbols from the parent of this symbol
     *                  (recursively).
     *
     * @returns the first symbol with a given name, in the order of appearance in this scope
     *          or any of the parent scopes (conditionally).
     */
    public resolveSync(name: string, localOnly = false): BaseSymbol | undefined {
        return this.#parent?.resolveSync(name, localOnly);
    }
 
    /**
     * @param t The type of objects to return.
     *
     * @returns the next enclosing parent of the given type.
     */
    public getParentOfType<T extends BaseSymbol>(t: SymbolConstructor<T>): T | undefined {
        let run = this.#parent;
        while (run) {
            if (run instanceof t) {
                return run;
            }
            run = run.parent;
        }

        return undefined;
    }
 
    /**
     * Creates a qualified identifier from this symbol and its parent.
     * If `full` is true then all parents are traversed in addition to this instance.
     *
     * @param separator The string to be used between the parts.
     * @param full A flag indicating if the full path is to be returned.
     * @param includeAnonymous Use a special string for empty scope names.
     *
     * @returns the constructed qualified identifier.
     */
    public qualifiedName(separator = ".", full = false, includeAnonymous = false): string {
        if (!includeAnonymous && this.name.length === 0) {
            return "";
        }
 
        let result: string = this.name.length === 0 ? "<anonymous>" : this.name;
        let run = this.#parent;
        while (run) {
            if (includeAnonymous || run.name.length > 0) {
                result = (run.name.length === 0 ? "<anonymous>" : run.name) + separator + result;
            }
 
            if (!full || !run.parent) {
                break;
            }
            run = run.parent;
        }
 
        return result;
    }
 
    /**
     * Type guard to check for ISymbolTable.
     *
     * @param candidate The object to check.
     *
     * @returns true if the object is a symbol table.
     */
    private isSymbolTable(candidate: unknown): candidate is ISymbolTable {
        return (candidate as ISymbolTable).info !== undefined;
    }
}