mirror of
https://github.com/h-sphere/sql-seal.git
synced 2026-07-22 10:10:28 +00:00
Merge pull request #103 from h-sphere/feat/js-syntax-highlighting
feat: adding JS syntax highlighting (to be used in Charts view)
This commit is contained in:
commit
29e84c3ccd
9 changed files with 431 additions and 10 deletions
|
|
@ -1,3 +1,6 @@
|
|||
# 0.28.1 (2025-03-14)
|
||||
- Adding syntax highlighting for javascript (to be used with GRAPH view)
|
||||
|
||||
# 0.28.0 (2025-03-14)
|
||||
- Added TEMPLATE view that allow to render your template with custom Handlebars template.
|
||||
- Improved syntax highlighting - now lines with errors will get highlighted with appropriate colour to indicate the issue
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "sqlseal",
|
||||
"name": "SQLSeal",
|
||||
"version": "0.28.0",
|
||||
"version": "0.28.1",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Use SQL in your notes to query your vault files and CSV content.",
|
||||
"author": "hypersphere",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "sqlseal",
|
||||
"version": "0.28.0",
|
||||
"version": "0.28.1",
|
||||
"description": "A plugin for Obsidian that allows you to run SQL queries on your notes.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
@ -23,6 +23,8 @@
|
|||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/esprima": "^4.0.6",
|
||||
"@types/estraverse": "^5.1.7",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/lodash": "^4.17.13",
|
||||
"@types/node": "^22.9.3",
|
||||
|
|
@ -56,6 +58,8 @@
|
|||
"ag-grid-community": "^32.3.3",
|
||||
"comlink": "^4.4.2",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"esprima": "^4.0.1",
|
||||
"estraverse": "^5.3.0",
|
||||
"handlebars": "^4.7.8",
|
||||
"json5": "^2.2.3",
|
||||
"jsonpath": "^1.1.1",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,12 @@ importers:
|
|||
esbuild-plugin-polyfill-node:
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.0(esbuild@0.24.0)
|
||||
esprima:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1
|
||||
estraverse:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.0
|
||||
handlebars:
|
||||
specifier: ^4.7.8
|
||||
version: 4.7.8
|
||||
|
|
@ -78,6 +84,12 @@ importers:
|
|||
specifier: ^11.0.4
|
||||
version: 11.0.4
|
||||
devDependencies:
|
||||
'@types/esprima':
|
||||
specifier: ^4.0.6
|
||||
version: 4.0.6
|
||||
'@types/estraverse':
|
||||
specifier: ^5.1.7
|
||||
version: 5.1.7
|
||||
'@types/jest':
|
||||
specifier: ^29.5.14
|
||||
version: 29.5.14
|
||||
|
|
@ -1046,6 +1058,12 @@ packages:
|
|||
'@types/emscripten@1.39.13':
|
||||
resolution: {integrity: sha512-cFq+fO/isvhvmuP/+Sl4K4jtU6E23DoivtbO4r50e3odaxAiVdbfSYRDdJ4gCdxx+3aRjhphS5ZMwIH4hFy/Cw==}
|
||||
|
||||
'@types/esprima@4.0.6':
|
||||
resolution: {integrity: sha512-lIk+kSt9lGv5hxK6aZNjiUEGZqKmOTpmg0tKiJQI+Ow98fLillxsiZNik5+RcP7mXL929KiTH/D9jGtpDlMbVw==}
|
||||
|
||||
'@types/estraverse@5.1.7':
|
||||
resolution: {integrity: sha512-JRVtdKYZz7VkNp7hMC/WKoiZ8DS3byw20ZGoMZ1R8eBrBPIY7iBaDAS1zcrnXQCwK44G4vbXkimeU7R0VLG8UQ==}
|
||||
|
||||
'@types/estree@1.0.6':
|
||||
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
|
||||
|
||||
|
|
@ -1356,8 +1374,8 @@ packages:
|
|||
peerDependencies:
|
||||
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||
|
||||
acorn@8.14.0:
|
||||
resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
|
||||
acorn@8.14.1:
|
||||
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
|
|
@ -4225,6 +4243,14 @@ snapshots:
|
|||
|
||||
'@types/emscripten@1.39.13': {}
|
||||
|
||||
'@types/esprima@4.0.6':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
|
||||
'@types/estraverse@5.1.7':
|
||||
dependencies:
|
||||
'@types/estree': 1.0.6
|
||||
|
||||
'@types/estree@1.0.6': {}
|
||||
|
||||
'@types/graceful-fs@4.1.9':
|
||||
|
|
@ -4598,11 +4624,11 @@ snapshots:
|
|||
dependencies:
|
||||
safari-14-idb-fix: 1.0.6
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.14.0):
|
||||
acorn-jsx@5.3.2(acorn@8.14.1):
|
||||
dependencies:
|
||||
acorn: 8.14.0
|
||||
acorn: 8.14.1
|
||||
|
||||
acorn@8.14.0: {}
|
||||
acorn@8.14.1: {}
|
||||
|
||||
ag-charts-types@10.3.3: {}
|
||||
|
||||
|
|
@ -5158,8 +5184,8 @@ snapshots:
|
|||
|
||||
espree@10.3.0:
|
||||
dependencies:
|
||||
acorn: 8.14.0
|
||||
acorn-jsx: 5.3.2(acorn@8.14.0)
|
||||
acorn: 8.14.1
|
||||
acorn-jsx: 5.3.2(acorn@8.14.1)
|
||||
eslint-visitor-keys: 4.2.0
|
||||
|
||||
esprima@1.2.2: {}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ const markDecorations = {
|
|||
blockTable: Decoration.mark({ class: 'cm-sqlseal-block-table' }),
|
||||
identifier: Decoration.mark({ class: 'cm-sqlseal-identifier' }),
|
||||
literal: Decoration.mark({ class: 'cm-sqlseal-literal' }),
|
||||
number: Decoration.mark({ class: 'cm-sqlseal-literal' }),
|
||||
string: Decoration.mark({ class: 'cm-sqlseal-literal' }),
|
||||
parameter: Decoration.mark({ class: 'cm-sqlseal-parameter' }),
|
||||
comment: Decoration.mark({ class: 'cm-sqlseal-comment' }),
|
||||
keyword: Decoration.mark({ class: 'cm-sqlseal-keyword' }),
|
||||
|
|
|
|||
358
src/grammar/highlighter/jsHighlighter.ts
Normal file
358
src/grammar/highlighter/jsHighlighter.ts
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
// @ts-nocheck
|
||||
import * as esprima from 'esprima';
|
||||
import * as estraverse from 'estraverse';
|
||||
|
||||
interface Decorator {
|
||||
type: 'keyword' | 'identifier' | 'string' | 'string-escape' | 'string-interpolation' |
|
||||
'number' | 'comment' | 'function' | 'operator' | 'punctuation' | 'regex' | 'error';
|
||||
start: number;
|
||||
end: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Highlights JavaScript syntax using Esprima and estraverse
|
||||
* @param source - The JavaScript source code to highlight
|
||||
* @returns Array of decorator objects for syntax highlighting
|
||||
*/
|
||||
function highlightJavaScript(source: string): Decorator[] {
|
||||
const decorators: Decorator[] = [];
|
||||
|
||||
// Keywords in JavaScript
|
||||
const keywords = new Set([
|
||||
'var', 'let', 'const', 'if', 'else', 'switch', 'case', 'default', 'for', 'while', 'do',
|
||||
'break', 'continue', 'return', 'function', 'class', 'extends', 'new', 'this', 'super',
|
||||
'import', 'export', 'from', 'as', 'async', 'await', 'try', 'catch', 'finally', 'throw',
|
||||
'typeof', 'instanceof', 'in', 'of', 'void', 'delete', 'yield', 'static', 'get', 'set',
|
||||
'true', 'false', 'null', 'undefined', 'NaN', 'Infinity'
|
||||
]);
|
||||
|
||||
// Helper function to add a decorator
|
||||
function addDecorator(type: Decorator['type'], start: number, end: number): void {
|
||||
if (start >= 0 && end > start && end <= source.length) {
|
||||
decorators.push({ type, start, end });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse the source code with location information and comment handling
|
||||
const ast = esprima.parseScript(source, {
|
||||
loc: true,
|
||||
range: true,
|
||||
comment: true,
|
||||
tokens: true,
|
||||
jsx: true
|
||||
});
|
||||
|
||||
// Process comments (they're separate from the AST in Esprima)
|
||||
if (ast.comments) {
|
||||
for (const comment of ast.comments) {
|
||||
addDecorator('comment', comment.range![0], comment.range![1]);
|
||||
}
|
||||
}
|
||||
|
||||
// Process tokens for basic syntax elements
|
||||
if (ast.tokens) {
|
||||
for (const token of ast.tokens) {
|
||||
switch (token.type) {
|
||||
case 'Keyword':
|
||||
addDecorator('keyword', token.range[0], token.range[1]);
|
||||
break;
|
||||
case 'Identifier':
|
||||
// We'll handle function identifiers separately
|
||||
addDecorator('identifier', token.range[0], token.range[1]);
|
||||
break;
|
||||
case 'Punctuator':
|
||||
if (['+', '-', '*', '/', '%', '=', '>', '<', '!', '&', '|', '^', '~', '?', ':'].includes(token.value) ||
|
||||
token.value.length > 1 && /^[+\-*/%=><!\^&|?:]+$/.test(token.value)) {
|
||||
addDecorator('operator', token.range[0], token.range[1]);
|
||||
} else {
|
||||
addDecorator('punctuation', token.range[0], token.range[1]);
|
||||
}
|
||||
break;
|
||||
case 'String':
|
||||
// String literals include the quotes
|
||||
addDecorator('string', token.range[0], token.range[1]);
|
||||
|
||||
// Highlight string content differently if needed
|
||||
const stringContent = source.substring(token.range[0] + 1, token.range[1] - 1);
|
||||
|
||||
// Look for escape sequences within the string
|
||||
const escapeRegex = /\\./g;
|
||||
let escapeMatch;
|
||||
while ((escapeMatch = escapeRegex.exec(stringContent)) !== null) {
|
||||
const escapeStart = token.range[0] + 1 + escapeMatch.index;
|
||||
const escapeEnd = escapeStart + escapeMatch[0].length;
|
||||
// Add specific decorator for escape sequences
|
||||
addDecorator('string-escape', escapeStart, escapeEnd);
|
||||
}
|
||||
|
||||
// Look for string interpolation patterns like ${...} if needed
|
||||
// This is mostly relevant for template literals, which are handled separately
|
||||
break;
|
||||
case 'Numeric':
|
||||
addDecorator('number', token.range[0], token.range[1]);
|
||||
break;
|
||||
case 'RegularExpression':
|
||||
addDecorator('regex', token.range[0], token.range[1]);
|
||||
break;
|
||||
case 'Boolean':
|
||||
case 'Null':
|
||||
addDecorator('keyword', token.range[0], token.range[1]);
|
||||
break;
|
||||
case 'Template':
|
||||
// Handle template literals more carefully - they may contain expressions
|
||||
// The token itself might be just a part of the template (a quasi)
|
||||
addDecorator('string', token.range[0], token.range[1]);
|
||||
|
||||
// Check if it's a template with expressions
|
||||
const templateText = source.substring(token.range[0], token.range[1]);
|
||||
if (templateText.includes('${')) {
|
||||
// This is handled in the AST traversal for TemplateLiteral nodes
|
||||
// But we can add specific handling here if needed
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use estraverse to traverse the AST and identify more complex patterns
|
||||
estraverse.traverse(ast, {
|
||||
enter: function (node: any, parent: any) {
|
||||
switch (node.type) {
|
||||
case 'FunctionDeclaration':
|
||||
// Function name
|
||||
if (node.id) {
|
||||
addDecorator('function', node.id.range[0], node.id.range[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'MethodDefinition':
|
||||
// Method name in classes
|
||||
if (node.key) {
|
||||
addDecorator('function', node.key.range[0], node.key.range[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Property':
|
||||
// Methods in object literals
|
||||
if (node.method && node.key) {
|
||||
addDecorator('function', node.key.range[0], node.key.range[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'VariableDeclarator':
|
||||
// Find arrow functions and function expressions assigned to variables
|
||||
if (node.init &&
|
||||
(node.init.type === 'ArrowFunctionExpression' ||
|
||||
node.init.type === 'FunctionExpression')) {
|
||||
addDecorator('function', node.id.range[0], node.id.range[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'CallExpression':
|
||||
// Highlight function calls
|
||||
if (node.callee.type === 'Identifier') {
|
||||
// Don't change the styling of built-in functions/methods or keywords
|
||||
const name = node.callee.name;
|
||||
if (!keywords.has(name)) {
|
||||
addDecorator('function', node.callee.range[0], node.callee.range[1]);
|
||||
}
|
||||
}
|
||||
else if (node.callee.type === 'MemberExpression' &&
|
||||
node.callee.property.type === 'Identifier') {
|
||||
// Method calls (obj.method())
|
||||
addDecorator('function', node.callee.property.range[0], node.callee.property.range[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ClassDeclaration':
|
||||
// Class name
|
||||
if (node.id) {
|
||||
addDecorator('identifier', node.id.range[0], node.id.range[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ImportDeclaration':
|
||||
// Import specifiers
|
||||
for (const specifier of node.specifiers) {
|
||||
if (specifier.local) {
|
||||
addDecorator('identifier', specifier.local.range[0], specifier.local.range[1]);
|
||||
}
|
||||
if (specifier.imported) {
|
||||
addDecorator('identifier', specifier.imported.range[0], specifier.imported.range[1]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ExportNamedDeclaration':
|
||||
case 'ExportDefaultDeclaration':
|
||||
// Handle export names
|
||||
if (node.declaration && node.declaration.id) {
|
||||
// For function/class declarations
|
||||
addDecorator('identifier', node.declaration.id.range[0], node.declaration.id.range[1]);
|
||||
}
|
||||
if (node.specifiers) {
|
||||
// For export { x, y }
|
||||
for (const specifier of node.specifiers) {
|
||||
if (specifier.local) {
|
||||
addDecorator('identifier', specifier.local.range[0], specifier.local.range[1]);
|
||||
}
|
||||
if (specifier.exported) {
|
||||
addDecorator('identifier', specifier.exported.range[0], specifier.exported.range[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'TemplateLiteral':
|
||||
// Handle template literals and their expressions
|
||||
for (const quasi of node.quasis) {
|
||||
// Mark the entire quasi segment as a string
|
||||
addDecorator('string', quasi.range[0], quasi.range[1]);
|
||||
|
||||
// Look for escape sequences within the template literal
|
||||
const quasiText = source.substring(quasi.range[0], quasi.range[1]);
|
||||
const escapeRegex = /\\./g;
|
||||
let escapeMatch;
|
||||
|
||||
while ((escapeMatch = escapeRegex.exec(quasiText)) !== null) {
|
||||
const escapeStart = quasi.range[0] + escapeMatch.index;
|
||||
const escapeEnd = escapeStart + escapeMatch[0].length;
|
||||
// You can add a specific decorator for escape sequences if desired
|
||||
// addDecorator('string-escape', escapeStart, escapeEnd);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle template expressions ${...}
|
||||
for (const expr of node.expressions) {
|
||||
// Find the ${
|
||||
const exprStart = expr.range[0] - 2; // For the ${ part
|
||||
if (exprStart >= 0 && source.substring(exprStart, exprStart + 2) === '${') {
|
||||
addDecorator('string-interpolation', exprStart, exprStart + 2);
|
||||
|
||||
// The expression itself will be handled by the normal traversal
|
||||
|
||||
// Find the closing }
|
||||
const closingBrace = expr.range[1]; // The end range should be right after the expression
|
||||
if (closingBrace < source.length && source[closingBrace] === '}') {
|
||||
addDecorator('string-interpolation', closingBrace, closingBrace + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Sort decorators by start position
|
||||
return decorators.sort((a, b) => a.start - b.start);
|
||||
} catch (error) {
|
||||
// If parsing fails, return an error decorator
|
||||
console.error('Parsing error:', error);
|
||||
return [{ type: 'error', start: 0, end: source.length }];
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to apply highlighting to HTML
|
||||
function applyHighlighting(source: string, decorators: Decorator[]): string {
|
||||
let html = '';
|
||||
let lastIndex = 0;
|
||||
|
||||
// First, resolve overlapping decorators by sorting and removing overlaps
|
||||
// We prioritize more specific types (like string-escape) over more general types (like string)
|
||||
const resolvedDecorators = resolveOverlappingDecorators(decorators);
|
||||
|
||||
for (const decorator of resolvedDecorators) {
|
||||
// Add any text before this decorator
|
||||
html += escapeHtml(source.substring(lastIndex, decorator.start));
|
||||
|
||||
// Add the decorated text
|
||||
const content = escapeHtml(source.substring(decorator.start, decorator.end));
|
||||
html += `<span class="js-${decorator.type}">${content}</span>`;
|
||||
|
||||
lastIndex = decorator.end;
|
||||
}
|
||||
|
||||
// Add any remaining text
|
||||
html += escapeHtml(source.substring(lastIndex));
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// Helper function to resolve overlapping decorators
|
||||
function resolveOverlappingDecorators(decorators: Decorator[]): Decorator[] {
|
||||
// First, sort by start position and then by length (shortest first)
|
||||
const sorted = [...decorators].sort((a, b) => {
|
||||
if (a.start !== b.start) return a.start - b.start;
|
||||
return (a.end - a.start) - (b.end - b.start);
|
||||
});
|
||||
|
||||
// Then resolve overlaps by splitting overlapping decorators
|
||||
const result: Decorator[] = [];
|
||||
let lastEnd = 0;
|
||||
|
||||
for (const decorator of sorted) {
|
||||
// Skip if this decorator is completely contained in the previous one
|
||||
if (decorator.start >= lastEnd) {
|
||||
result.push(decorator);
|
||||
lastEnd = decorator.end;
|
||||
} else if (decorator.end > lastEnd) {
|
||||
// Partial overlap - only add the non-overlapping part
|
||||
if (decorator.start < lastEnd && decorator.end > lastEnd) {
|
||||
result.push({
|
||||
type: decorator.type,
|
||||
start: lastEnd,
|
||||
end: decorator.end
|
||||
});
|
||||
lastEnd = decorator.end;
|
||||
}
|
||||
}
|
||||
// Completely contained - skip it
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Helper function to escape HTML special characters
|
||||
function escapeHtml(text: string): string {
|
||||
return text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
export { highlightJavaScript, applyHighlighting };
|
||||
|
||||
// Usage example:
|
||||
// import { highlightJavaScript, applyHighlighting } from './esprima-js-highlighter';
|
||||
//
|
||||
// const source = `
|
||||
// // This is a comment
|
||||
// function calculateTotal(items) {
|
||||
// return items
|
||||
// .map(item => item.price * item.quantity)
|
||||
// .reduce((total, value) => total + value, 0);
|
||||
// }
|
||||
//
|
||||
// class ShoppingCart {
|
||||
// constructor() {
|
||||
// this.items = [];
|
||||
// }
|
||||
//
|
||||
// addItem(item) {
|
||||
// this.items.push(item);
|
||||
// }
|
||||
//
|
||||
// get total() {
|
||||
// return calculateTotal(this.items);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// export default ShoppingCart;
|
||||
// `;
|
||||
//
|
||||
// const decorators = highlightJavaScript(source);
|
||||
// const html = applyHighlighting(source, decorators);
|
||||
// document.getElementById('code-display').innerHTML = html;
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import * as ohm from 'ohm-js';
|
||||
import { cstVisitor, Literal, parse } from 'sql-parser-cst';
|
||||
import highlightHandlebars from './highlighter/handlebarsHighlighter';
|
||||
import { highlightJavaScript } from './highlighter/jsHighlighter';
|
||||
|
||||
const nodes = new Map([
|
||||
['FlagExpression', { terminal: true, type: 'blockFlag' }],
|
||||
|
|
@ -93,6 +94,31 @@ export const highlighterOperation = (grammar: ohm.Grammar) => {
|
|||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
javascriptTemplate(_node) {
|
||||
let prefix = `async function x () {
|
||||
`
|
||||
let sufix = ' }'
|
||||
|
||||
|
||||
if (this.source.contents.trim()[0] === '{') {
|
||||
// we are parsing simple object, we need to adjust it
|
||||
prefix = `const data = `
|
||||
sufix = ';'
|
||||
}
|
||||
|
||||
const source = prefix + this.source.contents + sufix
|
||||
const offset = this.source.startIdx
|
||||
try {
|
||||
const decorators = highlightJavaScript(source)
|
||||
return decorators.map(d => ({
|
||||
type: d.type === 'keyword' ? 'template-keyword' : d.type,
|
||||
start: d.start - prefix.length + offset,
|
||||
end: d.end - prefix.length + offset
|
||||
})).filter(x => x.start >= offset)
|
||||
} catch (error) {
|
||||
return [{ type: 'error', start: this.source.startIdx, end: this.source.endIdx }]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export const SQLSealLangDefinition = (views: ViewDefinition[], flags: readonly F
|
|||
ExtraFlags = ${flagsDefinitions}
|
||||
anyObject = "{" (~selectKeyword any)*
|
||||
handlebarsTemplate = (~selectKeyword any)*
|
||||
javascriptTemplate = (~selectKeyword any)*
|
||||
selectKeyword = caseInsensitive<"WITH"> | caseInsensitive<"SELECT">
|
||||
tableKeyword = caseInsensitive<"TABLE">
|
||||
nl = "\n"
|
||||
|
|
|
|||
|
|
@ -49,5 +49,6 @@
|
|||
"0.25.0": "0.15.0",
|
||||
"0.26.0": "0.15.0",
|
||||
"0.27.0": "0.15.0",
|
||||
"0.28.0": "0.15.0"
|
||||
"0.28.0": "0.15.0",
|
||||
"0.28.1": "0.15.0"
|
||||
}
|
||||
Loading…
Reference in a new issue