Skip to content

Commit 5071dac

Browse files
committed
Release v0.4.0
1 parent a8005cc commit 5071dac

15 files changed

Lines changed: 138 additions & 34 deletions

File tree

CHANGELOG.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.4.0] - 2026-03-02
11+
12+
### Added
13+
14+
#### SQL Linter Engine (flowscope-core)
15+
- **Full SQL linting engine** with 60+ rules targeting SQLFluff parity
16+
- **Aliasing rules** (AL001–AL009): alias expression style, column alias quoting, unused aliases, duplicate column names, alias length limits, force-enable aliasing
17+
- **Ambiguity rules** (AM001–AM009): DISTINCT with GROUP BY, UNION type handling, ORDER BY column ordinals, ambiguous column counts, bare JOINs, set-column mismatches, implicit cross joins, limit without ORDER BY
18+
- **Capitalisation rules** (CP001–CP005): keyword, identifier, function name, literal, and type-annotation casing with consistent/upper/lower modes and `ignore_words_regex` support
19+
- **Convention rules** (CV001–CV012): operator style, COALESCE usage, trailing commas, COUNT row-style, IS NULL comparison, statement terminators, unnecessary brackets, LEFT JOIN preference, block quotes, quoted literals, CAST shorthand, implicit join detection
20+
- **Jinja rules** (JJ001): template tag detection with trim-marker parity
21+
- **Layout rules** (LT001–LT015): trailing whitespace, indentation, operator placement, comma position, long lines, function spacing, bracket placement, CTE newlines, SELECT modifiers, set operators, end-of-file newlines, start-of-file blanks, keyword line position, blank line limits
22+
- **Reference rules** (RF001–RF006): keyword references, qualified wildcards, single-table qualifications, keyword aliases, identifier quoting policies
23+
- **Structure rules** (ST001–ST012): ELSE/THEN on new lines, simple CASE, unused CTEs, nested CASE, subquery-to-CTE, column ordering, USING joins, DISTINCT parentheses, join condition order, constant predicates, unused joins, consecutive semicolons
24+
- **T-SQL rules** (TQ001–TQ003): SP_ prefix, SET NOCOUNT, batch separators
25+
- **Rule configuration system** supporting SQLFluff `.sqlfluff` config format with per-rule options (e.g., `capitalisation_policy`, `line_position`, `max_line_length`, `allow_scalar`)
26+
- **Dialect-aware detection** adapting rule behavior for PostgreSQL, BigQuery, Snowflake, Redshift, SparkSQL, MSSQL, and others
27+
- **Autofix metadata** on issues with `Safe`/`Unsafe` applicability markers and patch edits
28+
29+
#### CLI (flowscope-cli)
30+
- `--lint` flag for SQL linting with JSON, compact, and human-readable output formats
31+
- `--fix` flag for automatic SQL fixing with safe-only default and `--legacy-ast-fixes` for additional rewrites
32+
- `--fix-only` mode for applying fixes without re-linting
33+
- `--exclude-rules` for disabling specific lint rules
34+
- `--rule-configs-json` for passing rule configuration as JSON
35+
- `--show-fixes` to display applied fix details
36+
- `--jobs` for parallel multi-file lint/fix processing
37+
- `--no-respect-gitignore` for including gitignored files
38+
- Fix engine with iterative convergence loop, overlap recovery, and incremental rule evaluation budgets
39+
- Performance-tuned large-SQL handling with capped fix passes and targeted LT02 cleanup
40+
41+
#### CI
42+
- GitHub Actions CI workflow for lint, test, and parity validation
43+
44+
### Improved
45+
46+
#### Core Engine (flowscope-core)
47+
- Migrated all lint rules from regex to AST/tokenizer-driven detection for accuracy and performance
48+
- Shared document token stream across rules to avoid redundant tokenization
49+
- Token-span-based issue reporting for precise editor navigation
50+
51+
#### Web App (app/)
52+
- Integrated linter into the web app: lint is now enabled by default in the analysis worker
53+
- Updated WASM bindings to include lint API surface (`LintIssue`, `LintRequest`, `LintConfig` types)
54+
- Lint-aware issues panel with updated filtering
55+
- Updated mascot to purple-headed duck and added favicon, removed beta badge
56+
1057
## [0.3.1] - 2026-01-23
1158

1259
### Fixed

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ members = [
88
resolver = "2"
99

1010
[workspace.package]
11-
version = "0.3.1"
11+
version = "0.4.0"
1212
authors = ["PondPilot Team"]
1313
edition = "2021"
1414
license = "Apache-2.0"
1515
repository = "https://github.com/pondpilot/flowscope"
1616

1717
[workspace.dependencies]
18-
flowscope-core = { version = "0.3.1", path = "crates/flowscope-core", default-features = false }
19-
flowscope-export = { version = "0.3.1", path = "crates/flowscope-export", default-features = false }
18+
flowscope-core = { version = "0.4.0", path = "crates/flowscope-core", default-features = false }
19+
flowscope-export = { version = "0.4.0", path = "crates/flowscope-export", default-features = false }
2020
sqlparser = "0.59"
2121
serde = { version = "1.0", features = ["derive", "rc"] }
2222
serde_json = "1.0"

app/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
67
<title>FlowScope - SQL Lineage Analyzer</title>
78
<script>
89
(function() {

app/public/favicon.svg

Lines changed: 1 addition & 0 deletions
Loading

app/src/assets/duck-dark.svg

Lines changed: 1 addition & 1 deletion
Loading

app/src/assets/duck.svg

Lines changed: 1 addition & 1 deletion
Loading

app/src/components/FlowScopeLogo.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ export function FlowScopeLogo({ className }: FlowScopeLogoProps) {
2424
fill="currentColor"
2525
fillOpacity="0.3"
2626
/>
27-
{/* Head - green */}
27+
{/* Head - purple */}
2828
<path
2929
d="M31.5 27C24.0442 27 18 20.9558 18 13.5C18 6.04416 24.0442 3.25905e-07 31.5 0C38.9558 -3.25905e-07 45 6.04416 45 13.5C45 20.9558 38.9558 27 31.5 27Z"
30-
fill="#4CAE4F"
30+
fill="#A78BFA"
3131
/>
3232
{/* Eyes - dark blue */}
3333
<path

app/src/components/IssuesFilterBar.tsx

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ interface IssuesFilterBarProps {
3232
};
3333
/** Number of schema-related issues (UNKNOWN_COLUMN, UNKNOWN_TABLE, etc.) */
3434
schemaIssueCount?: number;
35+
/** Number of lint issues (codes starting with LINT_) */
36+
lintIssueCount?: number;
3537
/** Callback to open the schema editor */
3638
onOpenSchemaEditor?: () => void;
3739
className?: string;
@@ -58,6 +60,7 @@ export function IssuesFilterBar({
5860
availableSourceFiles,
5961
counts,
6062
schemaIssueCount,
63+
lintIssueCount,
6164
onOpenSchemaEditor,
6265
className,
6366
}: IssuesFilterBarProps) {
@@ -114,18 +117,24 @@ export function IssuesFilterBar({
114117
[filterState.sourceFiles, updateFilter]
115118
);
116119

120+
const toggleLintIssues = useCallback(() => {
121+
updateFilter({ showLintIssues: !filterState.showLintIssues });
122+
}, [filterState.showLintIssues, updateFilter]);
123+
117124
const clearAll = useCallback(() => {
118125
updateFilter({
119126
severity: 'all',
120127
codes: [],
121128
sourceFiles: [],
129+
showLintIssues: true,
122130
});
123131
}, [updateFilter]);
124132

125133
const hasActiveFilters =
126134
filterState.severity !== 'all' ||
127135
filterState.codes.length > 0 ||
128-
filterState.sourceFiles.length > 0;
136+
filterState.sourceFiles.length > 0 ||
137+
!filterState.showLintIssues;
129138

130139
return (
131140
<div className={cn('flex flex-col gap-2 px-4 py-2 border-b bg-muted/5', className)}>
@@ -247,8 +256,45 @@ export function IssuesFilterBar({
247256
</DropdownMenu>
248257
)}
249258

250-
{/* Right-aligned section: schema badge + clear all */}
259+
{/* Clear all button */}
260+
{hasActiveFilters && (
261+
<button
262+
onClick={clearAll}
263+
className="h-6 px-2 text-xs text-slate-500 hover:text-slate-900 dark:hover:text-slate-100 transition-colors"
264+
>
265+
Clear all
266+
</button>
267+
)}
268+
269+
{/* Right-aligned section: lint toggle + schema badge */}
251270
<div className="flex items-center gap-2 ml-auto">
271+
{/* Lint issues toggle */}
272+
{lintIssueCount !== undefined && lintIssueCount > 0 && (
273+
<button
274+
role="switch"
275+
aria-checked={filterState.showLintIssues}
276+
onClick={toggleLintIssues}
277+
className="flex items-center gap-2 h-8 text-xs font-medium text-slate-600 dark:text-slate-400"
278+
>
279+
<span>Lint</span>
280+
<span
281+
className={cn(
282+
'relative inline-flex h-4 w-7 shrink-0 items-center rounded-full transition-colors',
283+
filterState.showLintIssues
284+
? 'bg-slate-600 dark:bg-slate-400'
285+
: 'bg-slate-300 dark:bg-slate-600'
286+
)}
287+
>
288+
<span
289+
className={cn(
290+
'inline-block h-3 w-3 rounded-full bg-white shadow-sm transition-transform',
291+
filterState.showLintIssues ? 'translate-x-3.5' : 'translate-x-0.5'
292+
)}
293+
/>
294+
</span>
295+
</button>
296+
)}
297+
252298
{/* Schema issues badge */}
253299
{schemaIssueCount !== undefined && schemaIssueCount > 0 && onOpenSchemaEditor && (
254300
<button
@@ -264,16 +310,6 @@ export function IssuesFilterBar({
264310
<span className="text-xs font-medium">schema</span>
265311
</button>
266312
)}
267-
268-
{/* Clear all button */}
269-
{hasActiveFilters && (
270-
<button
271-
onClick={clearAll}
272-
className="h-6 px-2 text-xs text-slate-500 hover:text-slate-900 dark:hover:text-slate-100 transition-colors"
273-
>
274-
Clear all
275-
</button>
276-
)}
277313
</div>
278314
</div>
279315

app/src/components/SchemaAwareIssuesPanel.tsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ function isSchemaIssue(issue: Issue): boolean {
2121
return SCHEMA_ISSUE_CODES.includes(issue.code);
2222
}
2323

24+
function isLintIssue(issue: Issue): boolean {
25+
return issue.code.startsWith('LINT_');
26+
}
27+
2428
export function SchemaAwareIssuesPanel({
2529
projectId,
2630
onOpenSchemaEditor,
@@ -76,6 +80,11 @@ export function SchemaAwareIssuesPanel({
7680
// Apply filters to issues
7781
const filteredIssues = useMemo(() => {
7882
return sortedIssues.filter((issue) => {
83+
// Lint issues toggle
84+
if (!filterState.showLintIssues && isLintIssue(issue)) {
85+
return false;
86+
}
87+
7988
// Severity filter
8089
if (filterState.severity !== 'all' && issue.severity !== filterState.severity) {
8190
return false;
@@ -98,21 +107,28 @@ export function SchemaAwareIssuesPanel({
98107
});
99108
}, [sortedIssues, filterState, getIssueSourceName]);
100109

101-
// Calculate counts for the filter bar (total, not filtered)
110+
// Base issues after applying the lint toggle (used for counts and empty states)
111+
const baseIssues = useMemo(() => {
112+
if (filterState.showLintIssues) return sortedIssues;
113+
return sortedIssues.filter((i) => !isLintIssue(i));
114+
}, [sortedIssues, filterState.showLintIssues]);
115+
116+
// Calculate counts for the filter bar (respects lint toggle, ignores other filters)
102117
const counts = useMemo(() => {
103-
const errors = sortedIssues.filter((i) => i.severity === 'error').length;
104-
const warnings = sortedIssues.filter((i) => i.severity === 'warning').length;
105-
const infos = sortedIssues.filter((i) => i.severity === 'info').length;
118+
const errors = baseIssues.filter((i) => i.severity === 'error').length;
119+
const warnings = baseIssues.filter((i) => i.severity === 'warning').length;
120+
const infos = baseIssues.filter((i) => i.severity === 'info').length;
106121
return {
107-
all: sortedIssues.length,
122+
all: baseIssues.length,
108123
errors,
109124
warnings,
110125
infos,
111126
};
112-
}, [sortedIssues]);
127+
}, [baseIssues]);
113128

114-
// Count schema issues from total (not filtered) to keep banner stable
129+
// Count schema and lint issues from total (not filtered) to keep banner stable
115130
const schemaIssueCount = sortedIssues.filter(isSchemaIssue).length;
131+
const lintIssueCount = sortedIssues.filter(isLintIssue).length;
116132

117133
// Pre-compute locations for all filtered issues to avoid repeated calculations during render
118134
const issueLocationsMap = useMemo(
@@ -141,7 +157,8 @@ export function SchemaAwareIssuesPanel({
141157
const hasActiveFilters =
142158
filterState.severity !== 'all' ||
143159
filterState.codes.length > 0 ||
144-
filterState.sourceFiles.length > 0;
160+
filterState.sourceFiles.length > 0 ||
161+
!filterState.showLintIssues;
145162

146163
return (
147164
<div className="flex flex-col h-full bg-background">
@@ -153,6 +170,7 @@ export function SchemaAwareIssuesPanel({
153170
availableSourceFiles={availableSourceFiles}
154171
counts={counts}
155172
schemaIssueCount={schemaIssueCount}
173+
lintIssueCount={lintIssueCount}
156174
onOpenSchemaEditor={onOpenSchemaEditor}
157175
/>
158176
)}
@@ -172,7 +190,7 @@ export function SchemaAwareIssuesPanel({
172190
{/* Filtered count indicator */}
173191
{hasActiveFilters && (
174192
<p className="text-xs text-muted-foreground mb-3">
175-
Showing {filteredIssues.length} of {sortedIssues.length} issues
193+
Showing {filteredIssues.length} of {baseIssues.length} issues
176194
</p>
177195
)}
178196
{filteredIssues.map((issue, idx) => {

app/src/components/Workspace.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -318,10 +318,7 @@ export function Workspace({ backendReady, error, onRetry, isRetrying }: Workspac
318318
{/* Logo */}
319319
<div className="flex items-center gap-3">
320320
<FlowScopeLogo className="w-8 h-8 text-foreground/30 dark:text-white/30" />
321-
<div className="flex items-baseline gap-1">
322-
<span className="text-lg font-semibold text-foreground">FlowScope</span>
323-
<span className="text-xs font-mono text-muted-foreground">Beta</span>
324-
</div>
321+
<span className="text-lg font-semibold text-foreground">FlowScope</span>
325322
</div>
326323

327324
{/* Project Selector */}

0 commit comments

Comments
 (0)