zenstack
Version: 
FullStack enhancement for Prisma ORM: seamless integration from database to UI
218 lines • 11 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ZModelScopeProvider = exports.ZModelScopeComputation = void 0;
const ast_1 = require("@zenstackhq/language/ast");
const sdk_1 = require("@zenstackhq/sdk");
const langium_1 = require("langium");
const ts_pattern_1 = require("ts-pattern");
const ast_utils_1 = require("../utils/ast-utils");
const constants_1 = require("./constants");
const utils_1 = require("./validator/utils");
/**
 * Custom Langium ScopeComputation implementation which adds enum fields into global scope
 */
class ZModelScopeComputation extends langium_1.DefaultScopeComputation {
    constructor(services) {
        super(services);
        this.services = services;
    }
    computeExports(document, cancelToken) {
        const _super = Object.create(null, {
            computeExports: { get: () => super.computeExports }
        });
        return __awaiter(this, void 0, void 0, function* () {
            const result = yield _super.computeExports.call(this, document, cancelToken);
            // add enum fields so they can be globally resolved across modules
            for (const node of (0, langium_1.streamAllContents)(document.parseResult.value)) {
                if (cancelToken) {
                    yield (0, langium_1.interruptAndCheck)(cancelToken);
                }
                if ((0, ast_1.isEnumField)(node)) {
                    const desc = this.services.workspace.AstNodeDescriptionProvider.createDescription(node, node.name, document);
                    result.push(desc);
                }
            }
            return result;
        });
    }
    processNode(node, document, scopes) {
        super.processNode(node, document, scopes);
        if ((0, ast_1.isDataModel)(node) && !node.$baseMerged) {
            // add base fields to the scope recursively
            const bases = (0, sdk_1.getRecursiveBases)(node);
            for (const base of bases) {
                for (const field of base.fields) {
                    scopes.add(node, this.descriptions.createDescription(field, this.nameProvider.getName(field)));
                }
            }
        }
    }
}
exports.ZModelScopeComputation = ZModelScopeComputation;
class ZModelScopeProvider extends langium_1.DefaultScopeProvider {
    constructor(services) {
        super(services);
        this.services = services;
    }
    getGlobalScope(referenceType, context) {
        const model = (0, langium_1.getContainerOfType)(context.container, ast_1.isModel);
        if (!model) {
            return langium_1.EMPTY_SCOPE;
        }
        const importedUris = (0, langium_1.stream)(model.imports).map(ast_utils_1.resolveImportUri).nonNullable();
        const importedElements = this.indexManager.allElements(referenceType).filter((des) => {
            var _a;
            // allow current document
            return (0, langium_1.equalURI)(des.documentUri, (_a = model.$document) === null || _a === void 0 ? void 0 : _a.uri) ||
                // allow stdlib
                des.documentUri.path.endsWith(constants_1.STD_LIB_MODULE_NAME) ||
                // allow plugin models
                des.documentUri.path.endsWith(constants_1.PLUGIN_MODULE_NAME) ||
                // allow imported documents
                importedUris.some((importedUri) => (0, langium_1.equalURI)(des.documentUri, importedUri));
        });
        return new langium_1.StreamScope(importedElements);
    }
    getScope(context) {
        if ((0, ast_1.isMemberAccessExpr)(context.container) && context.container.operand && context.property === 'member') {
            return this.getMemberAccessScope(context);
        }
        if ((0, ast_1.isReferenceExpr)(context.container) && context.property === 'target') {
            // when reference expression is resolved inside a collection predicate, the scope is the collection
            const containerCollectionPredicate = getCollectionPredicateContext(context.container);
            if (containerCollectionPredicate) {
                return this.getCollectionPredicateScope(context, containerCollectionPredicate);
            }
        }
        return super.getScope(context);
    }
    getMemberAccessScope(context) {
        const referenceType = this.reflection.getReferenceType(context);
        const globalScope = this.getGlobalScope(referenceType, context);
        const node = context.container;
        // typedef's fields are only added to the scope if the access starts with `auth().`
        // or the member access resides inside a typedef
        const allowTypeDefScope = (0, utils_1.isAuthOrAuthMemberAccess)(node.operand) || !!(0, langium_1.getContainerOfType)(node, ast_1.isTypeDef);
        return (0, ts_pattern_1.match)(node.operand)
            .when(ast_1.isReferenceExpr, (operand) => {
            var _a;
            // operand is a reference, it can only be a model/type-def field
            const ref = operand.target.ref;
            if ((0, ast_1.isDataModelField)(ref) || (0, ast_1.isTypeDefField)(ref)) {
                return this.createScopeForContainer((_a = ref.type.reference) === null || _a === void 0 ? void 0 : _a.ref, globalScope, allowTypeDefScope);
            }
            return langium_1.EMPTY_SCOPE;
        })
            .when(ast_1.isMemberAccessExpr, (operand) => {
            var _a, _b;
            // operand is a member access, it must be resolved to a non-array model/typedef type
            const ref = operand.member.ref;
            if ((0, ast_1.isDataModelField)(ref) && !ref.type.array) {
                return this.createScopeForContainer((_a = ref.type.reference) === null || _a === void 0 ? void 0 : _a.ref, globalScope, allowTypeDefScope);
            }
            if ((0, ast_1.isTypeDefField)(ref) && !ref.type.array) {
                return this.createScopeForContainer((_b = ref.type.reference) === null || _b === void 0 ? void 0 : _b.ref, globalScope, allowTypeDefScope);
            }
            return langium_1.EMPTY_SCOPE;
        })
            .when(ast_1.isThisExpr, () => {
            // operand is `this`, resolve to the containing model
            return this.createScopeForContainingModel(node, globalScope);
        })
            .when(ast_1.isInvocationExpr, (operand) => {
            // deal with member access from `auth()` and `future()
            if ((0, sdk_1.isAuthInvocation)(operand)) {
                // resolve to `User` or `@@auth` decl
                return this.createScopeForAuth(node, globalScope);
            }
            if ((0, ast_utils_1.isFutureInvocation)(operand)) {
                // resolve `future()` to the containing model
                return this.createScopeForContainingModel(node, globalScope);
            }
            return langium_1.EMPTY_SCOPE;
        })
            .otherwise(() => langium_1.EMPTY_SCOPE);
    }
    getCollectionPredicateScope(context, collectionPredicate) {
        const referenceType = this.reflection.getReferenceType(context);
        const globalScope = this.getGlobalScope(referenceType, context);
        const collection = collectionPredicate.left;
        // typedef's fields are only added to the scope if the access starts with `auth().`
        const allowTypeDefScope = (0, utils_1.isAuthOrAuthMemberAccess)(collection);
        return (0, ts_pattern_1.match)(collection)
            .when(ast_1.isReferenceExpr, (expr) => {
            var _a;
            // collection is a reference - model or typedef field
            const ref = expr.target.ref;
            if ((0, ast_1.isDataModelField)(ref) || (0, ast_1.isTypeDefField)(ref)) {
                return this.createScopeForContainer((_a = ref.type.reference) === null || _a === void 0 ? void 0 : _a.ref, globalScope, allowTypeDefScope);
            }
            return langium_1.EMPTY_SCOPE;
        })
            .when(ast_1.isMemberAccessExpr, (expr) => {
            var _a;
            // collection is a member access, it can only be resolved to a model or typedef field
            const ref = expr.member.ref;
            if ((0, ast_1.isDataModelField)(ref) || (0, ast_1.isTypeDefField)(ref)) {
                return this.createScopeForContainer((_a = ref.type.reference) === null || _a === void 0 ? void 0 : _a.ref, globalScope, allowTypeDefScope);
            }
            return langium_1.EMPTY_SCOPE;
        })
            .when(sdk_1.isAuthInvocation, (expr) => {
            return this.createScopeForAuth(expr, globalScope);
        })
            .otherwise(() => langium_1.EMPTY_SCOPE);
    }
    createScopeForContainingModel(node, globalScope) {
        const model = (0, langium_1.getContainerOfType)(node, ast_1.isDataModel);
        if (model) {
            return this.createScopeForContainer(model, globalScope);
        }
        else {
            return langium_1.EMPTY_SCOPE;
        }
    }
    createScopeForContainer(node, globalScope, includeTypeDefScope = false) {
        if ((0, ast_1.isDataModel)(node)) {
            return this.createScopeForNodes((0, sdk_1.getModelFieldsWithBases)(node), globalScope);
        }
        else if (includeTypeDefScope && (0, ast_1.isTypeDef)(node)) {
            return this.createScopeForNodes(node.fields, globalScope);
        }
        else {
            return langium_1.EMPTY_SCOPE;
        }
    }
    createScopeForAuth(node, globalScope) {
        // get all data models and type defs from loaded and reachable documents
        const decls = (0, ast_utils_1.getAllLoadedAndReachableDataModelsAndTypeDefs)(this.services.shared.workspace.LangiumDocuments, (0, langium_1.getContainerOfType)(node, ast_1.isDataModel));
        const authDecl = (0, sdk_1.getAuthDecl)(decls);
        if (authDecl) {
            return this.createScopeForContainer(authDecl, globalScope, true);
        }
        else {
            return langium_1.EMPTY_SCOPE;
        }
    }
}
exports.ZModelScopeProvider = ZModelScopeProvider;
function getCollectionPredicateContext(node) {
    let curr = node;
    while (curr) {
        if (curr.$container && (0, ast_utils_1.isCollectionPredicate)(curr.$container) && curr.$containerProperty === 'right') {
            return curr.$container;
        }
        curr = curr.$container;
    }
    return undefined;
}
//# sourceMappingURL=zmodel-scope.js.map