UNPKG

zenstack

Version:

FullStack enhancement for Prisma ORM: seamless integration from database to UI

325 lines 13.4 kB
"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.ZModelCompletionProvider = 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 vscode_languageserver_1 = require("vscode-languageserver"); class ZModelCompletionProvider extends langium_1.DefaultCompletionProvider { constructor(services) { super(services); this.services = services; this.completionOptions = { triggerCharacters: ['@', '(', ',', '.'], }; } getCompletion(document, params) { const _super = Object.create(null, { getCompletion: { get: () => super.getCompletion } }); return __awaiter(this, void 0, void 0, function* () { try { return yield _super.getCompletion.call(this, document, params); } catch (e) { console.error('Completion error:', e.message); return undefined; } }); } completionFor(context, next, acceptor) { if ((0, ast_1.isDataModelAttribute)(context.node) || (0, ast_1.isDataModelFieldAttribute)(context.node)) { const completions = this.getCompletionFromHint(context.node); if (completions) { completions.forEach((c) => acceptor(context, c)); return; } } return super.completionFor(context, next, acceptor); } getCompletionFromHint(contextNode) { // get completion based on the hint on the next unfilled parameter const unfilledParams = this.getUnfilledAttributeParams(contextNode); const nextParam = unfilledParams[0]; if (!nextParam) { return undefined; } const hintAttr = (0, sdk_1.getAttribute)(nextParam, '@@@completionHint'); if (hintAttr) { const hint = hintAttr.args[0]; if (hint === null || hint === void 0 ? void 0 : hint.value) { if ((0, ast_1.isArrayExpr)(hint.value)) { return hint.value.items.map((item) => { return { label: `${item.value}`, kind: vscode_languageserver_1.CompletionItemKind.Value, detail: 'Parameter', sortText: '0', }; }); } } } return undefined; } // TODO: this doesn't work when the file contains parse errors getUnfilledAttributeParams(contextNode) { var _a; try { const params = (_a = contextNode.decl.ref) === null || _a === void 0 ? void 0 : _a.params; if (params) { const args = contextNode.args; let unfilledParams = [...params]; args.forEach((arg) => { if (arg.name) { unfilledParams = unfilledParams.filter((p) => p.name !== arg.name); } else { unfilledParams.shift(); } }); return unfilledParams; } } catch (_b) { // noop } return []; } completionForCrossReference(context, // eslint-disable-next-line @typescript-eslint/no-explicit-any crossRef, acceptor) { if (crossRef.property === 'member' && !(0, ast_1.isMemberAccessExpr)(context.node)) { // for guarding an error in the base implementation return; } const customAcceptor = (context, item) => { var _a, _b; // attributes starting with @@@ are for internal use only if (((_a = item.insertText) === null || _a === void 0 ? void 0 : _a.startsWith('@@@')) || ((_b = item.label) === null || _b === void 0 ? void 0 : _b.startsWith('@@@'))) { return; } if ('nodeDescription' in item) { const node = this.getAstNode(item.nodeDescription); if (!node) { return; } // enums in stdlib are not supposed to be referenced directly if (((0, ast_1.isEnum)(node) || (0, ast_1.isEnumField)(node)) && (0, sdk_1.isFromStdlib)(node)) { return; } if (((0, ast_1.isDataModelAttribute)(context.node) || (0, ast_1.isDataModelFieldAttribute)(context.node)) && !this.filterAttributeApplicationCompletion(context.node, node)) { // node not matching attribute context return; } } acceptor(context, item); }; return super.completionForCrossReference(context, crossRef, customAcceptor); } completionForKeyword(context, // eslint-disable-next-line @typescript-eslint/no-explicit-any keyword, acceptor) { const customAcceptor = (context, item) => { if (!this.filterKeywordForContext(context, keyword.value)) { return; } acceptor(context, item); }; return super.completionForKeyword(context, keyword, customAcceptor); } filterKeywordForContext(context, keyword) { if ((0, ast_1.isInvocationExpr)(context.node)) { return ['true', 'false', 'null', 'this'].includes(keyword); } else if ((0, ast_1.isDataModelAttribute)(context.node) || (0, ast_1.isDataModelFieldAttribute)(context.node)) { const exprContext = this.getAttributeContextType(context.node); if (exprContext === 'DefaultValue') { return ['true', 'false', 'null'].includes(keyword); } else { return ['true', 'false', 'null', 'this'].includes(keyword); } } else { return true; } } filterAttributeApplicationCompletion(contextNode, node) { const attrContextType = this.getAttributeContextType(contextNode); if ((0, ast_1.isFunctionDecl)(node) && attrContextType) { // functions are excluded if they are not allowed in the current context const funcExprContextAttr = (0, sdk_1.getAttribute)(node, '@@@expressionContext'); if (funcExprContextAttr && funcExprContextAttr.args[0]) { const arg = funcExprContextAttr.args[0]; if ((0, ast_1.isArrayExpr)(arg.value)) { return arg.value.items.some((item) => (0, sdk_1.isEnumFieldReference)(item) && item.target.$refText === attrContextType); } } return false; } if ((0, ast_1.isDataModelField)(node)) { // model fields are not allowed in @default return attrContextType !== 'DefaultValue'; } return true; } getAttributeContextType(node) { return (0, ts_pattern_1.match)(node.decl.$refText) .with('@default', () => 'DefaultValue') .with(ts_pattern_1.P.union('@@allow', '@allow', '@@deny', '@deny'), () => 'AccessPolicy') .with('@@validate', () => 'ValidationRule') .otherwise(() => undefined); } createReferenceCompletionItem(nodeDescription) { const node = this.getAstNode(nodeDescription); const documentation = this.getNodeDocumentation(node); return (0, ts_pattern_1.match)(node) .when(ast_1.isDataModel, () => ({ nodeDescription, kind: vscode_languageserver_1.CompletionItemKind.Class, detail: 'Data model', sortText: '1', documentation, })) .when(ast_1.isDataModelField, () => ({ nodeDescription, kind: vscode_languageserver_1.CompletionItemKind.Field, detail: 'Data model field', sortText: '0', documentation, })) .when(ast_1.isEnum, () => ({ nodeDescription, kind: vscode_languageserver_1.CompletionItemKind.Class, detail: 'Enum', sortText: '1', documentation, })) .when(ast_1.isEnumField, () => ({ nodeDescription, kind: vscode_languageserver_1.CompletionItemKind.Enum, detail: 'Enum value', sortText: '1', documentation, })) .when(ast_1.isFunctionDecl, () => ({ nodeDescription, insertText: this.getFunctionInsertText(nodeDescription), kind: vscode_languageserver_1.CompletionItemKind.Function, detail: 'Function', sortText: '1', documentation, })) .when(ast_1.isAttribute, () => ({ nodeDescription, insertText: this.getAttributeInsertText(nodeDescription), kind: vscode_languageserver_1.CompletionItemKind.Property, detail: 'Attribute', sortText: '1', documentation, })) .otherwise(() => ({ nodeDescription, kind: vscode_languageserver_1.CompletionItemKind.Reference, detail: nodeDescription.type, sortText: '2', documentation, })); } getFunctionInsertText(nodeDescription) { const node = this.getAstNode(nodeDescription); if ((0, ast_1.isFunctionDecl)(node)) { if (node.params.some((p) => !p.optional)) { return nodeDescription.name; } } return `${nodeDescription.name}()`; } getAttributeInsertText(nodeDescription) { const node = this.getAstNode(nodeDescription); if ((0, ast_1.isAttribute)(node)) { if (node.name === '@relation') { return `${nodeDescription.name}(fields: [], references: [])`; } } return nodeDescription.name; } getAstNode(nodeDescription) { let node = nodeDescription.node; if (!node) { const doc = this.services.shared.workspace.LangiumDocuments.getOrCreateDocument(nodeDescription.documentUri); if (!doc) { return undefined; } node = this.services.workspace.AstNodeLocator.getAstNode(doc.parseResult.value, nodeDescription.path); if (!node) { return undefined; } } return node; } getNodeDocumentation(node) { if (!node) { return undefined; } const md = this.commentsToMarkdown(node); return { kind: 'markdown', value: md, }; } commentsToMarkdown(node) { var _a; const md = (_a = this.services.documentation.DocumentationProvider.getDocumentation(node)) !== null && _a !== void 0 ? _a : ''; const zModelGenerator = new sdk_1.ZModelCodeGenerator(); const docs = []; try { (0, ts_pattern_1.match)(node) .when(ast_1.isAttribute, (attr) => { const zModelGenerator = new sdk_1.ZModelCodeGenerator(); docs.push('```prisma', zModelGenerator.generate(attr), '```'); }) .when(ast_1.isFunctionDecl, (func) => { docs.push('```ts', zModelGenerator.generate(func), '```'); }) .when(ast_1.isDataModel, (model) => { docs.push('```prisma', `model ${model.name} { ... }`, '```'); }) .when(ast_1.isEnum, (enumDecl) => { docs.push('```prisma', zModelGenerator.generate(enumDecl), '```'); }) .when(ast_1.isDataModelField, (field) => { var _a, _b; docs.push(`${field.name}: ${(_a = field.type.type) !== null && _a !== void 0 ? _a : (_b = field.type.reference) === null || _b === void 0 ? void 0 : _b.$refText}`); }) .otherwise((ast) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const name = ast.name; if (name) { docs.push(name); } }); } catch (_b) { // noop } if (md) { docs.push('___', md); } return docs.join('\n'); } } exports.ZModelCompletionProvider = ZModelCompletionProvider; //# sourceMappingURL=zmodel-completion-provider.js.map