Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## Unreleased

- Added dedicated syntax scopes for generated metadata headers, including markers, fields, values, dates, TODO placeholders, update authors and history entries.
- Added normalization of loose legacy metadata-style comment headers to the current SQLovely metadata format.
- Added metadata-header processing for every detected procedure, function and trigger in multi-object SQL scripts.
- Added version/history synchronization so `Version` matches the latest history entry and invalid version jumps are coerced to one-step bumps.
- Added multiline metadata descriptions with automatic wrapping to the configured line-length limit while preserving manual line breaks.
- Added `Updated By` metadata support, including migration from legacy updated-by aliases such as `geändert von` and `geupdated von`.
- Normalized metadata date values and history dates to `YYYY-MM-DD`, including common day-first and year-first separators.
- Expanded metadata regression coverage for legacy headers, multiline descriptions, date normalization, version synchronization and multi-object scripts.

## 0.1.7

- Consolidated developer documentation into `docs/DEVELOPMENT.md` and `docs/SQL_IMPLEMENTATION.md`.
Expand Down
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ npm run package:vsix
- Keep `README.md` user-facing.
- Put maintainer/developer details in `docs/`.
- Update `CHANGELOG.md` for user-visible changes.
- Update user-facing metadata examples when metadata-header layout or migration behavior changes.

For development setup and implementation notes, see:

Expand Down
1 change: 1 addition & 0 deletions PACKAGING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ Before publishing or attaching a VSIX to a release:
6. Run `npm run package:vsix`.
7. Install the generated VSIX in VS Code and open a `.sql` file.
8. Verify highlighting, the active dialect command, formatting, metadata-header quick fixes and the packaging output.
9. Smoke-test metadata headers for multi-object scripts, legacy-header migration, description wrapping, date normalization and `Updated By` values.
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ It is focused on Watcom SQL by default and also includes a small MSSQL-oriented

- SQL syntax highlighting for regular `.sql` files
- Conservative SQL formatting
- Optional metadata-header extras for procedures, functions and triggers
- Optional metadata-header extras for every detected procedure, function and trigger in a script
- Diagnostics and quick fixes for supported SQLovely rules
- Legacy metadata-header normalization with version/history synchronization
- Watcom SQL as the default dialect
- Basic MSSQL support for common T-SQL-style files

Expand Down Expand Up @@ -76,15 +77,18 @@ 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. Generated metadata headers also receive dedicated syntax scopes so themes can distinguish the markers, field names, versions, dates, TODO placeholders and history entries:
The current extra inserts or updates metadata headers for every detected procedure, function and trigger in a script. Generated metadata headers also receive dedicated syntax scopes so themes can distinguish the markers, field names, versions, dates, TODO placeholders, update authors and history entries:

```sql
CREATE PROCEDURE dbo.my_proc()
-- METADATA
--
-- Description : <TODO>
-- Description : First line of the description
-- Manually kept second line, with automatic wrapping if the
-- configured line-length limit would be exceeded.
-- Version : 1.0
-- Author : my-user
-- Updated By : my-user
-- Created : 2026-06-09
-- Updated : 2026-06-09
--
Expand All @@ -97,7 +101,11 @@ BEGIN
END;
```

The header is inserted directly before the first `BEGIN` line of the detected object. Repeated runs update the existing SQLovely block instead of duplicating it.
Headers are inserted directly before each detected object's first `BEGIN` line. Repeated runs update existing SQLovely blocks instead of duplicating them.

When SQLovely finds a loose legacy metadata-style comment block for a detected object, it normalizes the block to the generated SQLovely format instead of adding a second header. Legacy detection supports common `--`, `//`, `/` and simple block-comment styles, but stays conservative and requires a recognizable version field so regular explanatory comments are left in place.

Metadata updates also normalize date values to `YYYY-MM-DD`, preserve multiline descriptions, wrap long description lines to `sqlovely.diagnostics.maxLineLength.limit`, and keep manual description line breaks. `Version` is synchronized with the latest history entry: newer valid history entries update the field, version bumps add missing history entries, and invalid jumps are coerced to a one-step bump.

Extras are applied during normal SQLovely formatting by default:

Expand Down Expand Up @@ -144,7 +152,7 @@ The syntax grammar is intentionally broad. Dialect-specific behavior is handled

SQLovely can show diagnostics for supported rules, such as:

- missing metadata headers for procedures, functions and triggers
- missing metadata headers for procedures, functions and triggers, including multi-object scripts
- SQL lines longer than the configured line-length limit

Diagnostics do not modify files by themselves. Quick fixes can apply supported changes when selected.
Expand All @@ -169,7 +177,7 @@ Diagnostics do not modify files by themselves. Quick fixes can apply supported c
- no full Watcom SQL parser
- no full T-SQL parser
- no automatic dialect migration
- one primary SQL object per file is assumed for metadata-header insertion
- loose legacy metadata detection is best effort and intentionally conservative

## License

Expand Down
15 changes: 15 additions & 0 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ Useful smoke tests:
- run **SQLovely: Format SQL Files in Directory**
- run **SQLovely: Insert or Update Metadata Header**
- verify diagnostics and quick fixes
- verify metadata headers in a script with multiple procedures, functions or triggers
- verify loose legacy metadata headers are normalized only when they contain a recognizable version field
- verify long metadata descriptions are wrapped without removing manual line breaks

## Settings during development

Expand Down Expand Up @@ -91,3 +94,15 @@ For MSSQL-oriented smoke tests:
**SQLovely: Format SQL Files in Directory** uses the normal `sqlovely.format.*` settings.

It intentionally does not apply SQLovely Extras. Keep this behavior conservative because the command can touch many files at once.

## Metadata-header regression focus

When changing metadata-header behavior, add or update regression tests for:

- generated SQLovely headers
- loose legacy header normalization
- multi-object scripts
- version/history synchronization
- `Updated By` migration and preservation
- date normalization
- multiline description wrapping and manual line-break preservation
12 changes: 10 additions & 2 deletions docs/SQL_IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,15 @@ 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 | section markers, field names, versions, dates, TODO placeholders and history entries | inserted/updated through extras | supported for one primary object per file |
| Metadata headers | section markers, field names, versions, dates, TODO placeholders, update authors and history entries | inserted/updated through extras | supported for every detected procedure/function/trigger in a script |

## Metadata-header behavior

SQLovely metadata headers are managed per detected procedure, function and trigger. The metadata extra scans the full document, scopes each existing header to its nearest SQL object, and inserts or updates the header directly before that object's body where possible.

Loose legacy metadata-style comments are normalized only when a recognizable version field is present. This keeps regular comments from being rewritten accidentally while still supporting common dashed, slash-style and simple block-comment legacy headers.

Metadata updates normalize supported date formats to `YYYY-MM-DD`, preserve and wrap multiline descriptions, add the `Updated By` field, and synchronize the `Version` field with the latest history entry. Version bumps are constrained to a single logical step, such as `1.0` to `1.1`, `1.0` to `2.0`, or `1.0.0` to `1.0.1`.

## Design boundaries

Expand All @@ -75,6 +83,6 @@ SQLovely currently does not provide:
- schema-aware analysis
- IntelliSense
- validation of nested SQL blocks
- guaranteed parsing of every possible legacy metadata style
- dialect-exclusive error reporting
- automatic conversion between Watcom SQL and MSSQL
- multi-object metadata-header management within one file
4 changes: 3 additions & 1 deletion src/codeActions/metadataHeaderCodeActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class SqlovelyMetadataHeaderCodeActionProvider implements vscode.CodeActionProvi
}

const dialect = getActiveDialect(document.uri);
const diagnosticsConfiguration = getDiagnosticsConfiguration(document.uri);
const issue = findMissingMetadataHeaderIssue(document.getText(), dialect);

if (!issue) {
Expand All @@ -53,7 +54,8 @@ class SqlovelyMetadataHeaderCodeActionProvider implements vscode.CodeActionProvi
);

const result = insertOrUpdateMetadataHeader(document.getText(), dialect, {
author: getDefaultAuthorName()
author: getDefaultAuthorName(),
maxLineLength: diagnosticsConfiguration.maxLineLength.limit
});

if (result.text === document.getText()) {
Expand Down
6 changes: 4 additions & 2 deletions src/commands/applyExtras.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as vscode from 'vscode';

import { COMMANDS } from '../constants';
import { getActiveDialect, getExtrasConfiguration } from '../config';
import { getActiveDialect, getDiagnosticsConfiguration, getExtrasConfiguration } from '../config';
import { applyExtras } from '../extras';
import { requireActiveSqlEditorContext } from '../editor/activeSqlEditor';
import { replaceDocumentText } from '../editor/replaceDocumentText';
Expand All @@ -28,10 +28,12 @@ export function registerApplySqlovelyExtrasCommand(): vscode.Disposable {
}

const dialect = getActiveDialect(activeContext.resource);
const diagnosticsConfiguration = getDiagnosticsConfiguration(activeContext.resource);
const originalText = activeContext.document.getText();
const result = applyExtras(originalText, dialect, {
author: getDefaultAuthorName(),
metadataHeaderEnabled: extraConfiguration.metadataHeader.enabled
metadataHeaderEnabled: extraConfiguration.metadataHeader.enabled,
maxLineLength: diagnosticsConfiguration.maxLineLength.limit
});

if (!result.changed) {
Expand Down
6 changes: 4 additions & 2 deletions src/commands/formatCurrentFile.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as vscode from 'vscode';

import { COMMANDS } from '../constants';
import { getActiveDialect, getExtrasConfiguration, getFormatConfiguration } from '../config';
import { getActiveDialect, getDiagnosticsConfiguration, getExtrasConfiguration, getFormatConfiguration } from '../config';
import { requireActiveSqlEditorContext } from '../editor/activeSqlEditor';
import { replaceDocumentText } from '../editor/replaceDocumentText';
import { formatSqlDocument } from '../formatter';
Expand All @@ -22,6 +22,7 @@ export function registerFormatCurrentFileCommand(): vscode.Disposable {
}

const extrasConfiguration = getExtrasConfiguration(activeContext.resource);
const diagnosticsConfiguration = getDiagnosticsConfiguration(activeContext.resource);
const originalText = activeContext.editor.document.getText();
const result = formatSqlDocument(originalText, getActiveDialect(activeContext.resource), {
keywordCase: formatConfiguration.keywordCase,
Expand All @@ -30,7 +31,8 @@ export function registerFormatCurrentFileCommand(): vscode.Disposable {
maxConsecutiveBlankLines: formatConfiguration.maxConsecutiveBlankLines,
ensureFinalNewline: formatConfiguration.ensureFinalNewline,
applyExtrasWithFormatting: extrasConfiguration.enabled && extrasConfiguration.applyWithFormatting,
metadataHeaderEnabled: extrasConfiguration.metadataHeader.enabled
metadataHeaderEnabled: extrasConfiguration.metadataHeader.enabled,
maxLineLength: diagnosticsConfiguration.maxLineLength.limit
});

if (!result.changed) {
Expand Down
6 changes: 4 additions & 2 deletions src/commands/insertOrUpdateMetadataHeader.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as vscode from 'vscode';

import { COMMANDS } from '../constants';
import { getActiveDialect, getExtrasConfiguration } from '../config';
import { getActiveDialect, getDiagnosticsConfiguration, getExtrasConfiguration } from '../config';
import { insertOrUpdateMetadataHeader } from '../extras';
import { requireActiveSqlEditorContext } from '../editor/activeSqlEditor';
import { replaceDocumentText } from '../editor/replaceDocumentText';
Expand All @@ -28,9 +28,11 @@ export function registerInsertOrUpdateMetadataHeaderCommand(): vscode.Disposable
}

const dialect = getActiveDialect(activeContext.resource);
const diagnosticsConfiguration = getDiagnosticsConfiguration(activeContext.resource);
const originalText = activeContext.document.getText();
const result = insertOrUpdateMetadataHeader(originalText, dialect, {
author: getDefaultAuthorName()
author: getDefaultAuthorName(),
maxLineLength: diagnosticsConfiguration.maxLineLength.limit
});

if (result.action === 'skipped') {
Expand Down
6 changes: 2 additions & 4 deletions src/diagnostics/documentDiagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as vscode from 'vscode';
import { getActiveDialect, getExtrasConfiguration, getDiagnosticsConfiguration } from '../config';
import { SQL_LANGUAGE_ID } from '../constants';
import {
findMissingMetadataHeaderIssue,
findMissingMetadataHeaderIssues,
MISSING_METADATA_HEADER_DIAGNOSTIC_CODE,
SQL_OVELY_DIAGNOSTIC_SOURCE
} from './metadataHeaderDiagnostics';
Expand Down Expand Up @@ -77,9 +77,7 @@ export function updateSqlovelyDiagnosticsForDocument(

if (shouldCheckMissingMetadataHeader) {
const dialect = getActiveDialect(document.uri);
const issue = findMissingMetadataHeaderIssue(document.getText(), dialect);

if (issue) {
for (const issue of findMissingMetadataHeaderIssues(document.getText(), dialect)) {
const diagnostic = new vscode.Diagnostic(
new vscode.Range(document.positionAt(issue.startIndex), document.positionAt(issue.endIndex)),
issue.message,
Expand Down
1 change: 1 addition & 0 deletions src/diagnostics/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export {
findMissingMetadataHeaderIssue,
findMissingMetadataHeaderIssues,
MISSING_METADATA_HEADER_DIAGNOSTIC_CODE,
SQL_OVELY_DIAGNOSTIC_SOURCE,
type MissingMetadataHeaderIssue
Expand Down
40 changes: 25 additions & 15 deletions src/diagnostics/metadataHeaderDiagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { SqlDialect } from '../dialects';
import { findExistingMetadataHeader } from '../extras/metadataHeader';
import { detectPrimarySqlObject, type DetectedSqlObject } from '../extras/objectDetection';
import { detectSqlObjects, type DetectedSqlObject } from '../extras/objectDetection';

export const SQL_OVELY_DIAGNOSTIC_SOURCE = 'SQLovely';
export const MISSING_METADATA_HEADER_DIAGNOSTIC_CODE = 'sqlovely.missingMetadataHeader';
Expand All @@ -17,23 +17,33 @@ export function findMissingMetadataHeaderIssue(
text: string,
dialect: SqlDialect
): MissingMetadataHeaderIssue | undefined {
if (findExistingMetadataHeader(text)) {
return undefined;
}

const object = detectPrimarySqlObject(text, dialect);
return findMissingMetadataHeaderIssues(text, dialect)[0];
}

if (!object) {
return undefined;
export function findMissingMetadataHeaderIssues(
text: string,
dialect: SqlDialect
): readonly MissingMetadataHeaderIssue[] {
const objects = detectSqlObjects(text, dialect);
const issues: MissingMetadataHeaderIssue[] = [];

for (const [index, object] of objects.entries()) {
const nextObjectIndex = objects[index + 1]?.index ?? text.length;

if (findExistingMetadataHeader(text, object, nextObjectIndex)) {
continue;
}

issues.push({
code: MISSING_METADATA_HEADER_DIAGNOSTIC_CODE,
message: `SQLovely metadata header is missing for ${object.type} ${object.name}.`,
object,
startIndex: object.index,
endIndex: findDeclarationLineEnd(text, object.index)
});
}

return {
code: MISSING_METADATA_HEADER_DIAGNOSTIC_CODE,
message: `SQLovely metadata header is missing for ${object.type} ${object.name}.`,
object,
startIndex: object.index,
endIndex: findDeclarationLineEnd(text, object.index)
};
return issues;
}

function findDeclarationLineEnd(text: string, startIndex: number): number {
Expand Down
Loading
Loading