LCOV - code coverage report
Current view: top level - dashql-app/src/session/snippets - snippet_header.ts Coverage Total Hit
Test: DashQL 915828b Lines: 100.0 % 47 47
Test Date: 2026-11-06 19:30:32

            Line data    Source code
       1              : import * as dashql from '@ankoh/dashql-core';
       2              : 
       3              : import { ConnectorType } from '../../connections/connector_info.js';
       4              : import { SNIPPET_ENGINE_CONNECTORS } from './snippet.js';
       5              : 
       6              : /// One snippet file, `<engine>/<category>/<name>.sql`, a leading comment describes the snippet.
       7              : /// The files of the engines with the same category and name are the variants of one snippet.
       8              : export interface SnippetFile {
       9              :     /// The path of the file below the snippet folder
      10              :     path: string;
      11              :     /// The id of the snippet, `<category>/<name>`
      12              :     id: string;
      13              :     /// The category of the snippet, the folder of the file
      14              :     category: string;
      15              :     /// The connectors that the body of the file runs on, those of its engine
      16              :     connectors: readonly ConnectorType[];
      17              :     /// The text of the file, with its description
      18              :     body: string;
      19              : }
      20              : 
      21            1 : const encoder = new TextEncoder();
      22              : 
      23              : /// The comments of a snippet file
      24              : interface SnippetComments {
      25              :     /// The comment tag blocks, a snippet has none
      26              :     tags: dashql.CommentTag[];
      27              :     /// The ordinary comment blocks that don't lead the file, a snippet has none
      28              :     inner: number;
      29              :     /// The number of statements
      30              :     statements: number;
      31              : }
      32              : 
      33              : /// Read the comment blocks of a text with the DashQL parser
      34            1 : function readComments(core: dashql.DashQL, text: string, bytes: Uint8Array): SnippetComments {
      35            1 :     const catalog = core.createCatalog();
      36            1 :     const script = core.createScript(catalog);
      37            1 :     try {
      38            1 :         script.insertTextAt(0, text);
      39            1 :         script.parse();
      40            1 :         const parsed = script.getParsed();
      41            1 :         const reader = parsed.read();
      42              :         // The comments before the first statement describe the snippet
      43            1 :         const statements = reader.statementsLength();
      44            1 :         const bodyBegin = statements > 0 ? reader.statements(0)!.statementSpan()!.offset() : bytes.length;
      45            1 :         const block = new dashql.buffers.parser.CommentBlock();
      46            1 :         let inner = 0;
      47            1 :         for (let i = 0; i < reader.commentBlocksLength(); ++i) {
      48            1 :             reader.commentBlocks(i, block);
      49            1 :             if (block.blockType() === dashql.buffers.parser.CommentBlockType.COMMENT && block.textSpan()!.offset() >= bodyBegin) ++inner;
      50            1 :         }
      51            1 :         return { tags: dashql.readCommentTags(reader, bytes), inner, statements };
      52            1 :     } finally {
      53            1 :         script.destroy();
      54            1 :         catalog.destroy();
      55            1 :     }
      56            1 : }
      57              : 
      58              : /// Parse a snippet file, throws if the path or the comments are invalid
      59            1 : export function parseSnippetFile(core: dashql.DashQL, path: string, raw: string): SnippetFile {
      60            1 :     const pathMatch = /^([a-z0-9_-]+)\/([a-z0-9_-]+)\/([a-z0-9_-]+)\.sql$/.exec(path);
      61            1 :     if (pathMatch == null) {
      62            1 :         throw new Error(`invalid snippet path: ${path}`);
      63              :     }
      64            1 :     const [, engine, category, name] = pathMatch;
      65            1 :     const engineConnectors = SNIPPET_ENGINE_CONNECTORS.get(engine);
      66            1 :     if (engineConnectors === undefined) {
      67            1 :         throw new Error(`unknown engine folder "${engine}" of snippet ${path}`);
      68              :     }
      69              : 
      70            1 :     const bytes = encoder.encode(raw);
      71            1 :     const { tags, inner, statements } = readComments(core, raw, bytes);
      72              :     // The formatter of the preview moves comments within a statement, explanations belong into the description
      73            1 :     if (inner > 0) {
      74            1 :         throw new Error(`snippet ${path} has comments after its description, move them into the description`);
      75              :     }
      76            1 :     if (tags.length > 0) {
      77            1 :         throw new Error(`snippet ${path} has a @${tags[0].name} tag, snippets only have a description`);
      78              :     }
      79              :     // The description stays in the applied text, a snippet needs statements besides it
      80            1 :     if (statements === 0) {
      81            1 :         throw new Error(`snippet ${path} has no body`);
      82              :     }
      83            1 :     return {
      84            1 :         path,
      85            1 :         id: `${category}/${name}`,
      86            1 :         category,
      87            1 :         connectors: engineConnectors,
      88            1 :         body: raw.trim(),
      89            1 :     };
      90            1 : }
        

Generated by: LCOV version 2.0-1