From e1d81215c45249689ca09c4557c4d1de2c168620 Mon Sep 17 00:00:00 2001 From: MayerTim Date: Tue, 9 Jun 2026 22:58:09 +0200 Subject: [PATCH] feat(syntax): improve metadata header highlighting --- README.md | 2 +- docs/SQL_IMPLEMENTATION.md | 2 +- syntaxes/sqlovely.tmLanguage.json | 283 ++++++++++++++++++++++++++++-- test/runProjectValidationTests.js | 19 ++ 4 files changed, 292 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 09fe90b..5549fd2 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ Directory formatting intentionally applies formatting only. SQLovely Extras are SQLovely Extras are optional file-updating features beyond pure formatting. -The current extra inserts or updates a metadata header for procedures, functions and triggers: +The current extra inserts or updates a metadata header for procedures, functions and triggers. Generated metadata headers also receive dedicated syntax scopes so themes can distinguish the markers, field names, versions, dates, TODO placeholders and history entries: ```sql CREATE PROCEDURE dbo.my_proc() diff --git a/docs/SQL_IMPLEMENTATION.md b/docs/SQL_IMPLEMENTATION.md index fad877f..d0e7033 100644 --- a/docs/SQL_IMPLEMENTATION.md +++ b/docs/SQL_IMPLEMENTATION.md @@ -65,7 +65,7 @@ The grammar highlights useful lexical regions in `.sql` files. | Transactions and savepoints | yes | keyword casing | not object-relevant | | MSSQL batches with `GO` | yes | root-level keyword handling | not object-relevant | | MSSQL routines | yes | rudimentary formatting | procedure/function/trigger supported | -| Metadata headers | comments highlighted | inserted/updated through extras | supported for one primary object per file | +| Metadata headers | section markers, field names, versions, dates, TODO placeholders and history entries | inserted/updated through extras | supported for one primary object per file | ## Design boundaries diff --git a/syntaxes/sqlovely.tmLanguage.json b/syntaxes/sqlovely.tmLanguage.json index b1e7ae4..3c83e49 100644 --- a/syntaxes/sqlovely.tmLanguage.json +++ b/syntaxes/sqlovely.tmLanguage.json @@ -49,22 +49,89 @@ "metadataHeader": { "patterns": [ { - "name": "meta.header.sqlovely.sql", - "match": "^\\s*--\\s*(SQLovely-Metadata-(?:Start|End))\\b.*$", - "captures": { - "1": { - "name": "entity.name.tag.metadata.sqlovely.sql" + "name": "comment.block.metadata.sqlovely.sql", + "begin": "^(\\s*)(--)(\\s*)(METADATA)\\s*$", + "beginCaptures": { + "2": { + "name": "punctuation.definition.comment.sql.sqlovely" + }, + "4": { + "name": "entity.name.section.metadata.begin.sqlovely.sql" } - } + }, + "end": "^(\\s*)(--)(\\s*)(METADATA END)\\s*$", + "endCaptures": { + "2": { + "name": "punctuation.definition.comment.sql.sqlovely" + }, + "4": { + "name": "entity.name.section.metadata.end.sqlovely.sql" + } + }, + "patterns": [ + { + "include": "#metadataHeaderSeparators" + }, + { + "include": "#metadataHeaderDescription" + }, + { + "include": "#metadataHeaderVersion" + }, + { + "include": "#metadataHeaderAuthor" + }, + { + "include": "#metadataHeaderDates" + }, + { + "include": "#metadataHeaderHistory" + }, + { + "include": "#metadataHeaderGenericFields" + } + ] }, { - "name": "meta.header.field.sqlovely.sql", - "match": "^\\s*--\\s*(Object Type|Object Name|Dialect|Author|Created|Updated|Description)\\s*:", - "captures": { - "1": { - "name": "variable.other.metadata-field.sqlovely.sql" + "name": "comment.block.metadata.legacy.sqlovely.sql", + "begin": "^(\\s*)(--)(\\s*)(SQLovely-Metadata-Start)\\b.*$", + "beginCaptures": { + "2": { + "name": "punctuation.definition.comment.sql.sqlovely" + }, + "4": { + "name": "entity.name.section.metadata.begin.sqlovely.sql" } - } + }, + "end": "^(\\s*)(--)(\\s*)(SQLovely-Metadata-End)\\b.*$", + "endCaptures": { + "2": { + "name": "punctuation.definition.comment.sql.sqlovely" + }, + "4": { + "name": "entity.name.section.metadata.end.sqlovely.sql" + } + }, + "patterns": [ + { + "include": "#metadataHeaderDescription" + }, + { + "include": "#metadataHeaderVersion" + }, + { + "include": "#metadataHeaderAuthor" + }, + { + "include": "#metadataHeaderDates" + }, + { + "include": "#metadataHeaderHistory" + }, + { + "include": "#metadataHeaderGenericFields" + } + ] } ] }, @@ -373,6 +440,198 @@ "match": "\\." } ] + }, + "metadataHeaderSeparators": { + "patterns": [ + { + "name": "meta.separator.metadata.sqlovely.sql", + "match": "^(\\s*)(--)(\\s*)$", + "captures": { + "2": { + "name": "punctuation.definition.comment.sql.sqlovely" + } + } + } + ] + }, + "metadataHeaderDescription": { + "patterns": [ + { + "name": "meta.field.description.metadata.sqlovely.sql", + "begin": "^(\\s*)(--)(\\s*)(Description)(\\s*)(:)(\\s*)", + "beginCaptures": { + "2": { + "name": "punctuation.definition.comment.sql.sqlovely" + }, + "4": { + "name": "variable.other.property.metadata-field.sqlovely.sql" + }, + "6": { + "name": "punctuation.separator.key-value.metadata.sqlovely.sql" + } + }, + "end": "$", + "patterns": [ + { + "name": "comment.line.todo.metadata.placeholder.sqlovely.sql", + "match": "" + }, + { + "name": "string.unquoted.metadata.description.sqlovely.sql", + "match": "[^\\r\\n]+" + } + ] + } + ] + }, + "metadataHeaderVersion": { + "patterns": [ + { + "name": "meta.field.version.metadata.sqlovely.sql", + "begin": "^(\\s*)(--)(\\s*)(Version)(\\s*)(:)(\\s*)", + "beginCaptures": { + "2": { + "name": "punctuation.definition.comment.sql.sqlovely" + }, + "4": { + "name": "variable.other.property.metadata-field.sqlovely.sql" + }, + "6": { + "name": "punctuation.separator.key-value.metadata.sqlovely.sql" + } + }, + "end": "$", + "patterns": [ + { + "name": "constant.numeric.version.metadata.sqlovely.sql", + "match": "\\bv?\\d+(?:\\.\\d+)*(?:[-+][A-Za-z0-9.-]+)?\\b" + } + ] + } + ] + }, + "metadataHeaderAuthor": { + "patterns": [ + { + "name": "meta.field.author.metadata.sqlovely.sql", + "begin": "^(\\s*)(--)(\\s*)(Author)(\\s*)(:)(\\s*)", + "beginCaptures": { + "2": { + "name": "punctuation.definition.comment.sql.sqlovely" + }, + "4": { + "name": "variable.other.property.metadata-field.sqlovely.sql" + }, + "6": { + "name": "punctuation.separator.key-value.metadata.sqlovely.sql" + } + }, + "end": "$", + "patterns": [ + { + "name": "entity.name.other.author.metadata.sqlovely.sql", + "match": "[^\\r\\n]+" + } + ] + } + ] + }, + "metadataHeaderDates": { + "patterns": [ + { + "name": "meta.field.date.metadata.sqlovely.sql", + "begin": "^(\\s*)(--)(\\s*)(Created|Updated)(\\s*)(:)(\\s*)", + "beginCaptures": { + "2": { + "name": "punctuation.definition.comment.sql.sqlovely" + }, + "4": { + "name": "variable.other.property.metadata-field.sqlovely.sql" + }, + "6": { + "name": "punctuation.separator.key-value.metadata.sqlovely.sql" + } + }, + "end": "$", + "patterns": [ + { + "name": "constant.other.date.metadata.sqlovely.sql", + "match": "\\b\\d{4}-\\d{2}-\\d{2}\\b" + } + ] + } + ] + }, + "metadataHeaderHistory": { + "patterns": [ + { + "name": "meta.field.history.metadata.sqlovely.sql", + "match": "^(\\s*)(--)(\\s*)(History)(\\s*)(:)(\\s*)$", + "captures": { + "2": { + "name": "punctuation.definition.comment.sql.sqlovely" + }, + "4": { + "name": "variable.other.property.metadata-field.sqlovely.sql" + }, + "6": { + "name": "punctuation.separator.key-value.metadata.sqlovely.sql" + } + } + }, + { + "name": "meta.history-entry.metadata.sqlovely.sql", + "begin": "^(\\s*)(--)(\\s*)(v\\d+(?:\\.\\d+)*)(\\s*)(:)(\\s*)", + "beginCaptures": { + "2": { + "name": "punctuation.definition.comment.sql.sqlovely" + }, + "4": { + "name": "constant.numeric.version.metadata.sqlovely.sql" + }, + "6": { + "name": "punctuation.separator.key-value.metadata.sqlovely.sql" + } + }, + "end": "$", + "patterns": [ + { + "name": "constant.other.date.metadata.sqlovely.sql", + "match": "\\b\\d{4}-\\d{2}-\\d{2}\\b" + }, + { + "name": "entity.name.other.author.metadata.sqlovely.sql", + "match": "(?<=\\d{4}-\\d{2}-\\d{2}\\s)[^\\r\\n]+$" + } + ] + } + ] + }, + "metadataHeaderGenericFields": { + "patterns": [ + { + "name": "meta.field.generic.metadata.sqlovely.sql", + "begin": "^(\\s*)(--)(\\s*)(Object Type|Object Name|Dialect)(\\s*)(:)(\\s*)", + "beginCaptures": { + "2": { + "name": "punctuation.definition.comment.sql.sqlovely" + }, + "4": { + "name": "variable.other.property.metadata-field.sqlovely.sql" + }, + "6": { + "name": "punctuation.separator.key-value.metadata.sqlovely.sql" + } + }, + "end": "$", + "patterns": [ + { + "name": "string.unquoted.metadata.value.sqlovely.sql", + "match": "[^\\r\\n]+" + } + ] + } + ] } } } diff --git a/test/runProjectValidationTests.js b/test/runProjectValidationTests.js index d2e6b3f..14cb41c 100644 --- a/test/runProjectValidationTests.js +++ b/test/runProjectValidationTests.js @@ -244,6 +244,25 @@ runTest('SQLovely grammar exposes the expected repository sections', () => { assert.ok(repositories.includes('operators')); }); + +runTest('SQLovely grammar gives generated metadata headers semantic scopes', () => { + const grammarText = fs.readFileSync(path.join(root, 'syntaxes/sqlovely.tmLanguage.json'), 'utf8'); + + for (const fragment of [ + 'comment.block.metadata.sqlovely.sql', + 'entity.name.section.metadata.begin.sqlovely.sql', + 'entity.name.section.metadata.end.sqlovely.sql', + 'variable.other.property.metadata-field.sqlovely.sql', + 'punctuation.separator.key-value.metadata.sqlovely.sql', + 'constant.numeric.version.metadata.sqlovely.sql', + 'constant.other.date.metadata.sqlovely.sql', + 'comment.line.todo.metadata.placeholder.sqlovely.sql', + 'meta.history-entry.metadata.sqlovely.sql' + ]) { + assert.ok(grammarText.includes(fragment), `metadata grammar should include ${fragment}`); + } +}); + runTest('SQLovely grammar includes audited SQL lexical categories', () => { const grammarText = fs.readFileSync(path.join(root, 'syntaxes/sqlovely.tmLanguage.json'), 'utf8').toLowerCase();