From 65479ab6d94dbf4e3b68d95cb43521766a265041 Mon Sep 17 00:00:00 2001 From: NQPhuc <11730168+NQPhuc@users.noreply.github.com> Date: Thu, 13 Aug 2026 14:36:44 +0700 Subject: [PATCH 1/5] fix: resolve a dep write's block through the symbol table Every write to a Dep block found its block by re-reading the block's own source text and comparing endpoint names, and that re-reading resolves `a.b` by counting fragments on both sides at once. A mixed-level edge such as `a.x -> b` was therefore recorded as schema `a`, table `x`, which nothing matches: colour and note writes returned no edits at all, and every colour pick appended another block. A table-level target, whose field list is empty, matched any edge on the pair, so a colour picked on an aggregated line landed on the pair's first column-level block, where the fieldless-only read never looks. findDepDefinition walks DepMetadata, whose endpoints are already resolved to table and column symbols in both the block and the inline form, and compares the declarations they name, the way findRefDefinition does for a ref. A table-level target matches only an edge with no columns on either side. syncDep and updateElementSetting's dep path both go through it; the folder-private endpoint comparers are deleted with their last callers, and syncDep drops its unused pre-parsed-blocks parameter. Found while retiring the old matching: a column's inline `[dep: -> b]` naming a table does not compile, so two fixtures written that way were invalid DBML that the text comparison edited regardless. Both are now column-to-column. Co-Authored-By: Claude Opus 5 (1M context) --- packages/dbml-core/src/transform/index.ts | 5 +- .../examples/compiler/syncDep.test.ts | 101 ++++++++++++--- .../compiler/updateElementSetting.test.ts | 39 +++--- .../src/compiler/queries/transform/syncDep.ts | 69 ++++------ .../queries/transform/updateElementSetting.ts | 22 ++-- .../compiler/queries/transform/utils/dep.ts | 119 ++++++++++++++++++ .../compiler/queries/transform/utils/index.ts | 29 +---- 7 files changed, 269 insertions(+), 115 deletions(-) create mode 100644 packages/dbml-parse/src/compiler/queries/transform/utils/dep.ts diff --git a/packages/dbml-core/src/transform/index.ts b/packages/dbml-core/src/transform/index.ts index 2d95dea74..a49de2976 100644 --- a/packages/dbml-core/src/transform/index.ts +++ b/packages/dbml-core/src/transform/index.ts @@ -1,7 +1,7 @@ import { Compiler, DEFAULT_ENTRY, MemoryProjectLayout } from '@dbml/parse'; import type { DiagramViewSyncOperation, DiagramViewBlock, TextEdit, - DepSyncOperation, DepBlock, ElementIdentifier, TableIdentifier, + DepSyncOperation, ElementIdentifier, TableIdentifier, } from '@dbml/parse'; export { findDiagramViewBlocks, findDepBlocks } from '@dbml/parse'; @@ -28,12 +28,11 @@ export function syncDiagramView ( export function syncDep ( dbmlCode: string, operations: DepSyncOperation[], - blocks?: DepBlock[], ): { newDbml: string; edits: TextEdit[] } { const layout = new MemoryProjectLayout(); layout.setSource(DEFAULT_ENTRY, dbmlCode); const compiler = new Compiler(layout); - return compiler.syncDep(DEFAULT_ENTRY, operations, blocks); + return compiler.syncDep(DEFAULT_ENTRY, operations); } export function updateElementSettingEdit ( diff --git a/packages/dbml-parse/__tests__/examples/compiler/syncDep.test.ts b/packages/dbml-parse/__tests__/examples/compiler/syncDep.test.ts index 603d143e9..c71a55371 100644 --- a/packages/dbml-parse/__tests__/examples/compiler/syncDep.test.ts +++ b/packages/dbml-parse/__tests__/examples/compiler/syncDep.test.ts @@ -127,25 +127,21 @@ Dep { expect(db.deps[0].color).toBe('#abcabc'); }); - it('matches a table-level edge to a column-level block (implicit table dep)', () => { + it('leaves a column-level block alone when the target is table-level', () => { const dbml = `${PRELUDE} Dep { a.id -> b.id }`; - const { newDbml } = syncDep(dbml, [ + const { newDbml, edits } = syncDep(dbml, [ { operation: 'update', edge: tableEdge('a', 'b'), color: '#1abc9c' }, ]); - expect(newDbml).toContain('[color: #1abc9c]'); - // Should update in place, not create a duplicate - expect(newDbml.match(/Dep/g)?.length).toBe(1); - const db = interpret(newDbml).getValue()!; - expect(db.deps).toHaveLength(1); - expect(db.deps[0].color).toBe('#1abc9c'); + expect(edits).toHaveLength(0); + expect(newDbml).toBe(dbml); }); }); -describe('syncDep - create with table-level edge matching column-level block', () => { - it('treats an existing column-level block as an update when creating with a table-level edge', () => { +describe('syncDep - create with a table-level edge beside a column-level block', () => { + it('creates the direct block rather than colouring the column-level one', () => { const dbml = `${PRELUDE} Dep { a.id -> b.id @@ -153,12 +149,89 @@ Dep { const { newDbml } = syncDep(dbml, [ { operation: 'create', edge: tableEdge('a', 'b'), color: '#ff5733' }, ]); - // Should update existing block, not create a new one - expect(newDbml.match(/Dep/g)?.length).toBe(1); - expect(newDbml).toContain('[color: #ff5733]'); + expect(newDbml).toContain('Dep [color: #ff5733] {'); + expect(newDbml).toContain('a -> b'); + const db = interpret(newDbml).getValue()!; + expect(db.deps).toHaveLength(2); + const direct = db.deps.find((d) => d.edges[0].upstream.fieldNames.length === 0); + const columnLevel = db.deps.find((d) => d.edges[0].upstream.fieldNames.length === 1); + expect(direct?.color).toBe('#ff5733'); + expect(columnLevel?.color).toBeUndefined(); + }); +}); + +describe('syncDep - mixed-level edges', () => { + const mixedEdge = (): DepSyncOperation['edge'] => ({ + upstream: { tableName: 'a', fieldNames: ['id'] }, + downstream: { tableName: 'b', fieldNames: [] }, + }); + + const MIXED = `${PRELUDE} +Dep { + a.id -> b +}`; + + it('colors the block carrying the edge', () => { + const { newDbml } = syncDep(MIXED, [ + { operation: 'create', edge: mixedEdge(), color: '#0055ff' }, + ]); + const db = interpret(newDbml).getValue()!; + expect(db.deps).toHaveLength(1); + expect(db.deps[0].color).toBe('#0055ff'); + }); + + it('notes the block carrying the edge', () => { + const { newDbml } = syncDep(MIXED, [ + { operation: 'update', edge: mixedEdge(), note: 'from etl' }, + ]); const db = interpret(newDbml).getValue()!; expect(db.deps).toHaveLength(1); - expect(db.deps[0].color).toBe('#ff5733'); + expect(db.deps[0].note?.value).toBe('from etl'); + }); + + it('leaves one block after repeated picks', () => { + const first = syncDep(MIXED, [ + { operation: 'create', edge: mixedEdge(), color: '#0055ff' }, + ]).newDbml; + const second = syncDep(first, [ + { operation: 'create', edge: mixedEdge(), color: '#ff0055' }, + ]).newDbml; + const db = interpret(second).getValue()!; + expect(db.deps).toHaveLength(1); + expect(db.deps[0].color).toBe('#ff0055'); + }); + + it('tells a schema-qualified table apart from a table column', () => { + const dbml = ` +Table s.a { id int } +Table a { id int } +Table b { id int } + +Dep { + s.a -> b +} + +Dep { + a.id -> b +}`; + const { newDbml } = syncDep(dbml, [ + { + operation: 'create', + edge: { upstream: { schemaName: 's', tableName: 'a', fieldNames: [] }, downstream: { tableName: 'b', fieldNames: [] } }, + color: '#111111', + }, + { + operation: 'create', + edge: mixedEdge(), + color: '#222222', + }, + ]); + const db = interpret(newDbml).getValue()!; + expect(db.deps).toHaveLength(2); + const schemaLevel = db.deps.find((d) => d.edges[0].upstream.fieldNames.length === 0); + const columnLevel = db.deps.find((d) => d.edges[0].upstream.fieldNames.length === 1); + expect(schemaLevel?.color).toBe('#111111'); + expect(columnLevel?.color).toBe('#222222'); }); }); diff --git a/packages/dbml-parse/__tests__/examples/compiler/updateElementSetting.test.ts b/packages/dbml-parse/__tests__/examples/compiler/updateElementSetting.test.ts index 8e6d68d27..f703101e9 100644 --- a/packages/dbml-parse/__tests__/examples/compiler/updateElementSetting.test.ts +++ b/packages/dbml-parse/__tests__/examples/compiler/updateElementSetting.test.ts @@ -289,33 +289,38 @@ Table b { }); describe('updateElementSetting - inline dep', () => { - const dep = (up: string, down: string): ElementIdentifier => ({ + // An inline `[dep: -> b.id]` on a.id is the edge a.id -> b.id, so its identity carries both fields + const dep = (upTable: string, upField: string, downTable: string, downField: string): ElementIdentifier => ({ kind: MetadataKind.Dep, - upstream: { tableName: up }, - downstream: { tableName: down }, + upstream: { tableName: upTable, fieldNames: [upField] }, + downstream: { tableName: downTable, fieldNames: [downField] }, }); - it('extracts inline dep to standalone when adding a setting', () => { - const dbml = `Table a { - id int [dep: -> b] + const INLINE = `Table a { + id int [dep: -> b.id] } Table b { id int }`; - const result = update(dbml, dep('a', 'b'), 'color', '#FF0000'); - expect(result).not.toContain('[dep: -> b]'); + + it('extracts inline dep to standalone when adding a setting', () => { + const result = update(INLINE, dep('a', 'id', 'b', 'id'), 'color', '#FF0000'); + expect(result).not.toContain('[dep: -> b.id]'); expect(result).toContain('color: #FF0000'); - expect(result).toContain('a -> b'); + expect(result).toContain('a.id -> b.id'); }); it('does nothing when removing a setting from an inline dep', () => { - const dbml = `Table a { - id int [dep: -> b] -} -Table b { - id int -}`; - const result = update(dbml, dep('a', 'b'), 'color', null); - expect(result).toBe(dbml); + const result = update(INLINE, dep('a', 'id', 'b', 'id'), 'color', null); + expect(result).toBe(INLINE); + }); + + it('does nothing when the target is table-level and the inline dep is not', () => { + const result = update(INLINE, { + kind: MetadataKind.Dep, + upstream: { tableName: 'a' }, + downstream: { tableName: 'b' }, + } as ElementIdentifier, 'color', '#FF0000'); + expect(result).toBe(INLINE); }); }); diff --git a/packages/dbml-parse/src/compiler/queries/transform/syncDep.ts b/packages/dbml-parse/src/compiler/queries/transform/syncDep.ts index ff1cd3b21..ada917738 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/syncDep.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/syncDep.ts @@ -1,13 +1,13 @@ /** * Dep block transform - read and write `Dep` blocks in DBML source. * - * Parallel to {@link ./syncDiagramView.ts}, but a Dep block has no name to - * key on - a block is identified by its edge endpoints (upstream/downstream - * schema + table + fields). The color picker uses this to write a dep's + * Parallel to {@link ./syncDiagramView.ts}, but a Dep block has no name to key on, + * so a write is aimed at an edge and {@link findDepDefinition} finds the block or the + * inline `[dep: …]` setting carrying it. The color picker uses this to write a dep's * `[color]`: * - `update` an existing block's `[color]`, or * - `create` a direct `Dep { a -> b } [color: ]` when the picked - * (table-level) line has no backing block, or + * (table-level) line has no direct block, or * - `remove` an existing block's `[color]` so the dep falls back to its * upstream -> group -> grey default. * `create` treats an already-matching block as an `update`, so it never @@ -31,7 +31,8 @@ import { } from '@/core/types/nodes'; import { destructureComplexVariable, extractSettingName } from '@/core/utils/expression'; import type Compiler from '../../index'; -import { endpointMatches, formatEndpoint } from './utils'; +import { formatEndpoint, findDepDefinition } from './utils'; +import type { DepDefinition } from './utils'; import { TextEdit, applyTextEdits } from './applyTextEdits'; import { updateNoteEdit, removeNoteEdit, addNoteEdit } from '@/core/utils/note'; import { updateSettingEdit, removeSettingEdit } from '@/core/utils/setting'; @@ -80,19 +81,15 @@ export function syncDep ( this: Compiler, filepath: Filepath, operations: DepSyncOperation[], - blocks?: DepBlock[], ): { newDbml: string; edits: TextEdit[]; } { const dbml = this.getSource(filepath) ?? ''; - const program = this.parseFile(filepath).getValue().ast; - const originalBlocks = blocks ?? depBlocksFromProgram(program); - const inlineDeps = inlineDepsFromProgram(dbml, program); const allEdits: TextEdit[] = []; for (const op of operations) { - allEdits.push(...applyOperation(dbml, op, originalBlocks, inlineDeps)); + allEdits.push(...applyOperation(this, filepath, dbml, op)); } allEdits.sort((a, b) => b.start - a.start); @@ -270,69 +267,55 @@ function extractInlineDepEdges (field: FunctionApplicationNode, host: DepEndpoin return edges; } -function edgesMatch (candidate: DepSyncEdge, target: DepSyncEdge): boolean { - return endpointMatches(candidate.upstream, target.upstream) && endpointMatches(candidate.downstream, target.downstream); -} - -function blockHasEdge (block: DepBlock, edge: DepSyncEdge): boolean { - return block.edges.some((e) => edgesMatch(e, edge)); -} - -function findBlockForEdge (blocks: DepBlock[], edge: DepSyncEdge): DepBlock | undefined { - return blocks.find((b) => blockHasEdge(b, edge)); -} - /** Dispatch a single sync operation to the appropriate edit strategy. */ -function applyOperation (dbml: string, operation: DepSyncOperation, blocks: DepBlock[], inlineDeps: InlineDep[]): TextEdit[] { +function applyOperation (compiler: Compiler, filepath: Filepath, dbml: string, operation: DepSyncOperation): TextEdit[] { + const definition = findDepDefinition(compiler, filepath, operation.edge); + switch (operation.operation) { case 'create': - return computeCreateEdit(dbml, operation, blocks, inlineDeps); - case 'update': { - const block = findBlockForEdge(blocks, operation.edge); - return block ? computeUpdateEdit(operation, block, dbml) : []; - } - case 'remove': { - const block = findBlockForEdge(blocks, operation.edge); - return block ? computeUpdateEdit({ ...operation, color: undefined, note: undefined }, block, dbml) : []; - } + return computeCreateEdit(dbml, operation, definition); + case 'update': + return definition?.kind === 'block' ? computeUpdateEdit(operation, definition.declaration, dbml) : []; + case 'remove': + return definition?.kind === 'block' + ? computeUpdateEdit({ ...operation, color: undefined, note: undefined }, definition.declaration, dbml) + : []; default: return []; } } /** Compute edits to update an existing block's color and/or note. */ -function computeUpdateEdit (operation: DepSyncOperation, block: DepBlock, source: string): TextEdit[] { +function computeUpdateEdit (operation: DepSyncOperation, declaration: ElementDeclarationNode, source: string): TextEdit[] { const edits: TextEdit[] = []; if (operation.color !== undefined) { - const edit = updateSettingEdit(block.declaration, SettingName.Color, operation.color, source); + const edit = updateSettingEdit(declaration, SettingName.Color, operation.color, source); if (edit) edits.push(edit); } if (operation.note === null) { - const edit = removeNoteEdit(block.declaration); + const edit = removeNoteEdit(declaration); if (edit) edits.push(edit); } else if (operation.note !== undefined) { - const edit = updateNoteEdit(block.declaration, operation.note) ?? addNoteEdit(block.declaration, operation.note); + const edit = updateNoteEdit(declaration, operation.note) ?? addNoteEdit(declaration, operation.note); if (edit) edits.push(edit); } return edits; } -/** Compute edits to create a new Dep block (or update if one already matches). */ -function computeCreateEdit (dbml: string, operation: DepSyncOperation, blocks: DepBlock[], inlineDeps: InlineDep[]): TextEdit[] { - const existing = findBlockForEdge(blocks, operation.edge); - if (existing) return computeUpdateEdit(operation, existing, dbml); +/** Compute edits to create a new Dep block (or update if one already carries the edge). */ +function computeCreateEdit (dbml: string, operation: DepSyncOperation, definition: DepDefinition | undefined): TextEdit[] { + if (definition?.kind === 'block') return computeUpdateEdit(operation, definition.declaration, dbml); const newBlock = generateDepBlock(operation.edge, operation.color ?? ''); const createEdit: TextEdit = { start: dbml.length, end: dbml.length, newText: '\n\n' + newBlock + '\n' }; // If the edge is authored inline, strip the inline setting to avoid duplication. - const inline = inlineDeps.find((d) => edgesMatch(d.edge, operation.edge)); - if (inline) { + if (definition?.kind === 'inline') { return [ - { start: inline.fullStart, end: inline.fullEnd, newText: '' }, + { start: definition.fullStart, end: definition.fullEnd, newText: '' }, createEdit, ]; } diff --git a/packages/dbml-parse/src/compiler/queries/transform/updateElementSetting.ts b/packages/dbml-parse/src/compiler/queries/transform/updateElementSetting.ts index 6c9da8615..cde93d6be 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/updateElementSetting.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/updateElementSetting.ts @@ -6,10 +6,9 @@ import { applyTextEdits, type TextEdit } from './applyTextEdits'; import { updateSettingEdit } from '@/core/utils/setting'; import type { ElementIdentifier, DepIdentifier, RefIdentifier } from './types'; import { - endpointsEqual, endpointMatches, lookupElementSymbol, - formatEndpoint, formatSetting, findRefDefinition, + lookupElementSymbol, + formatEndpoint, formatSetting, findRefDefinition, findDepDefinition, } from './utils'; -import { depBlocksFromProgram, inlineDepsFromProgram } from './syncDep'; import { FunctionApplicationNode, type SyntaxNode } from '@/core/types/nodes'; export function updateElementSettingEdit ( @@ -75,19 +74,12 @@ function findNamedElementDeclaration (compiler: Compiler, filepath: Filepath, ta function updateDepSettingEdit (compiler: Compiler, filepath: Filepath, target: DepIdentifier, settingName: string, value: string | null | undefined): TextEdit[] { const source = compiler.getSource(filepath) ?? ''; - const program = compiler.parseFile(filepath).getValue().ast; - const block = depBlocksFromProgram(program).find((b) => - b.edges.some((e) => endpointsEqual(e.upstream, target.upstream) && endpointsEqual(e.downstream, target.downstream)), - ); + const definition = findDepDefinition(compiler, filepath, target); + if (!definition) return []; - if (!block) { + if (definition.kind === 'inline') { if (value === null) return []; - const inline = inlineDepsFromProgram(source, program).find((d) => - endpointMatches(d.edge.upstream, target.upstream) && endpointMatches(d.edge.downstream, target.downstream), - ); - if (!inline) return []; - const up = formatEndpoint(target.upstream); const down = formatEndpoint(target.downstream); const setting = formatSetting(settingName, value); @@ -96,12 +88,12 @@ function updateDepSettingEdit (compiler: Compiler, filepath: Filepath, target: D ? `Dep [${setting}] {\n ${up} -> ${down}\n}` : `Dep {\n ${up} -> ${down}\n}`; return [ - { start: inline.fullStart, end: inline.fullEnd, newText: '' }, + { start: definition.fullStart, end: definition.fullEnd, newText: '' }, { start: source.length, end: source.length, newText: '\n\n' + depBlock + '\n' }, ]; } - const { declaration } = block; + const { declaration } = definition; const { body } = declaration; if (body instanceof FunctionApplicationNode) { diff --git a/packages/dbml-parse/src/compiler/queries/transform/utils/dep.ts b/packages/dbml-parse/src/compiler/queries/transform/utils/dep.ts new file mode 100644 index 000000000..2baa8562c --- /dev/null +++ b/packages/dbml-parse/src/compiler/queries/transform/utils/dep.ts @@ -0,0 +1,119 @@ +import { SettingName } from '@/core/types/keywords'; +import { ElementDeclarationNode, FunctionApplicationNode, PrefixExpressionNode } from '@/core/types/nodes'; +import { Filepath } from '@/core/types/filepath'; +import { UNHANDLED } from '@/core/types/module'; +import { removeSettingEdit } from '@/core/utils/setting'; +import { DepMetadata } from '@/core/types/symbol/metadata'; +import { SymbolKind } from '@/core/types/symbol'; +import type { NodeSymbol } from '@/core/types/symbol'; +import type Compiler from '../../../index'; +import type { DepIdentifier, EndpointRef } from '../types'; +import { lookupElementSymbol, normalizeSchema } from './index'; + +export interface InlineDepDefinition { + kind: 'inline'; + op: string; + fullStart: number; + fullEnd: number; +} + +export interface BlockDepDefinition { + kind: 'block'; + declaration: ElementDeclarationNode; +} + +export type DepDefinition = InlineDepDefinition | BlockDepDefinition; + +interface ResolvedEndpoint { + table?: NodeSymbol; + columns: (NodeSymbol | undefined)[]; +} + +function resolveEndpoint (compiler: Compiler, filepath: Filepath, endpoint: EndpointRef): ResolvedEndpoint { + const table = lookupElementSymbol( + compiler, + filepath, + normalizeSchema(endpoint.schemaName), + endpoint.tableName, + SymbolKind.Table, + ); + const columns = (endpoint.fieldNames ?? []).map((name) => ( + table ? compiler.lookupMembers(table, SymbolKind.Column, name) : undefined + )); + + return { table, columns }; +} + +// Aliases and imports give the same element more than one symbol; its declaration is single. +function sameSymbol (left: NodeSymbol | undefined, right: NodeSymbol | undefined): boolean { + const leftDeclaration = left?.originalSymbol.declaration; + return !!leftDeclaration && leftDeclaration === right?.originalSymbol.declaration; +} + +function endpointMatches ( + table: NodeSymbol | undefined, + columns: NodeSymbol[], + target: ResolvedEndpoint, +): boolean { + if (!sameSymbol(table, target.table)) return false; + if (columns.length !== target.columns.length) return false; + return columns.every((column, i) => sameSymbol(column, target.columns[i])); +} + +/** + * Finds the `Dep` block or inline `[dep: …]` setting that carries the edge `target` names. + * + * Matching runs on resolved table and column symbols, the way {@link findRefDefinition} does. + * The endpoints' source text cannot be compared instead: `a.b` is `schema.table` or + * `table.field` depending on what `a` resolves to, so a mixed-level edge such as + * `a.x -> b` is not describable without the symbol table. + * + * A target with no field names matches only an edge with no columns on that side. + */ +export function findDepDefinition ( + compiler: Compiler, + filepath: Filepath, + target: DepIdentifier, +): DepDefinition | undefined { + const ast = compiler.parseFile(filepath).getValue().ast; + const programSymbol = compiler.nodeSymbol(ast).getFiltered(UNHANDLED); + if (!programSymbol) return undefined; + + const upstream = resolveEndpoint(compiler, filepath, target.upstream); + const downstream = resolveEndpoint(compiler, filepath, target.downstream); + if (!upstream.table || !downstream.table) return undefined; + + const source = compiler.getSource(filepath) ?? ''; + + for (const meta of compiler.symbolMetadata(programSymbol)) { + if (!(meta instanceof DepMetadata)) continue; + + const upstreamTables = meta.upstreamTables(compiler); + const upstreamColumns = meta.upstreamColumns(compiler); + const downstreamTables = meta.downstreamTables(compiler); + const downstreamColumns = meta.downstreamColumns(compiler); + + const carriesEdge = upstreamTables.some((_, i) => ( + endpointMatches(upstreamTables[i], upstreamColumns[i] ?? [], upstream) + && endpointMatches(downstreamTables[i], downstreamColumns[i] ?? [], downstream) + )); + if (!carriesEdge) continue; + + if (meta.declaration instanceof ElementDeclarationNode) { + return { kind: 'block', declaration: meta.declaration }; + } + + const columnField = meta.declaration.parentOfKind(FunctionApplicationNode); + if (!columnField) continue; + const fullEdit = removeSettingEdit(columnField, SettingName.Dep, source); + if (!fullEdit) continue; + const prefix = meta.declaration.value; + if (!(prefix instanceof PrefixExpressionNode)) continue; + const op = prefix.op?.value; + if (!op) continue; + + return { kind: 'inline', op, fullStart: fullEdit.start, fullEnd: fullEdit.end }; + } + + return undefined; +} diff --git a/packages/dbml-parse/src/compiler/queries/transform/utils/index.ts b/packages/dbml-parse/src/compiler/queries/transform/utils/index.ts index 2624c00c4..779a94a64 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/utils/index.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/utils/index.ts @@ -7,34 +7,17 @@ import { addDoubleQuoteIfNeeded, splitQualifiedIdentifier } from '../../utils'; import type { EndpointRef } from '../types'; export type { ElementIdentifier } from '../types'; export { findRefDefinition, type InlineRef, type StandaloneRef } from './ref'; +export { + findDepDefinition, + type DepDefinition, + type InlineDepDefinition, + type BlockDepDefinition, +} from './dep'; export function normalizeSchema (schema?: string | null): string { return schema && schema.length > 0 ? schema : DEFAULT_SCHEMA_NAME; } -export function endpointsEqual (left: EndpointRef, right: EndpointRef): boolean { - if (normalizeSchema(left.schemaName) !== normalizeSchema(right.schemaName)) - return false; - if (left.tableName !== right.tableName) return false; - const leftFieldNames = left.fieldNames ?? []; - const rightFieldNames = right.fieldNames ?? []; - if (leftFieldNames.length !== rightFieldNames.length) return false; - return leftFieldNames.every((leftField, i) => leftField === rightFieldNames[i]); -} - -export function endpointMatches ( - candidate: EndpointRef, - target: EndpointRef, -): boolean { - if (normalizeSchema(candidate.schemaName) !== normalizeSchema(target.schemaName)) return false; - if (candidate.tableName !== target.tableName) return false; - const targetFieldNames = target.fieldNames ?? []; - if (targetFieldNames.length === 0) return true; - const candidateFieldNames = candidate.fieldNames ?? []; - if (candidateFieldNames.length !== targetFieldNames.length) return false; - return candidateFieldNames.every((f, i) => f === targetFieldNames[i]); -} - export function normalizeTableName ( input: string | { schema?: string; table: string }, ): { From 8593ad45ef99440089b2ab382383b8c83a82c444 Mon Sep 17 00:00:00 2001 From: Huy-DNA Date: Thu, 13 Aug 2026 14:48:02 +0700 Subject: [PATCH 2/5] v10.1.1 --- dbml-playground/package.json | 6 +++--- lerna.json | 2 +- packages/dbml-cli/package.json | 8 ++++---- packages/dbml-connector/package.json | 2 +- packages/dbml-core/package.json | 4 ++-- packages/dbml-parse/package.json | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dbml-playground/package.json b/dbml-playground/package.json index f31c602c2..8fb652362 100644 --- a/dbml-playground/package.json +++ b/dbml-playground/package.json @@ -1,6 +1,6 @@ { "name": "@dbml/playground", - "version": "10.1.0", + "version": "10.1.1", "description": "Interactive playground for debugging and visualizing the DBML parser pipeline", "author": "Holistics ", "license": "Apache-2.0", @@ -26,8 +26,8 @@ "format": "prettier --write src/" }, "dependencies": { - "@dbml/core": "^10.1.0", - "@dbml/parse": "^10.1.0", + "@dbml/core": "^10.1.1", + "@dbml/parse": "^10.1.1", "@phosphor-icons/vue": "^2.2.0", "dompurify": "^3.4.13", "floating-vue": "^5.2.2", diff --git a/lerna.json b/lerna.json index 009955d9f..e3f6b68b4 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "10.1.0", + "version": "10.1.1", "npmClient": "yarn", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } diff --git a/packages/dbml-cli/package.json b/packages/dbml-cli/package.json index 8d78df541..81cc5ccb2 100644 --- a/packages/dbml-cli/package.json +++ b/packages/dbml-cli/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/cli", - "version": "10.1.0", + "version": "10.1.1", "description": "", "main": "lib/index.js", "license": "Apache-2.0", @@ -33,9 +33,9 @@ ], "dependencies": { "@babel/cli": "^7.21.0", - "@dbml/connector": "^10.1.0", - "@dbml/core": "^10.1.0", - "@dbml/parse": "^10.1.0", + "@dbml/connector": "^10.1.1", + "@dbml/core": "^10.1.1", + "@dbml/parse": "^10.1.1", "bluebird": "^3.5.5", "chalk": "^2.4.2", "commander": "^2.20.0", diff --git a/packages/dbml-connector/package.json b/packages/dbml-connector/package.json index 6eb0c8bc5..81cfa7804 100644 --- a/packages/dbml-connector/package.json +++ b/packages/dbml-connector/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/connector", - "version": "10.1.0", + "version": "10.1.1", "description": "This package was created to fetch the schema JSON from many kind of databases.", "author": "huy.phung.sw@gmail.com", "license": "MIT", diff --git a/packages/dbml-core/package.json b/packages/dbml-core/package.json index 9d1282eed..1fcd31a3b 100644 --- a/packages/dbml-core/package.json +++ b/packages/dbml-core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/core", - "version": "10.1.0", + "version": "10.1.1", "description": "> TODO: description", "author": "Holistics ", "license": "Apache-2.0", @@ -47,7 +47,7 @@ "lint:fix": "eslint --fix ." }, "dependencies": { - "@dbml/parse": "^10.1.0", + "@dbml/parse": "^10.1.1", "antlr4": "^4.13.1", "lodash": "^4.18.1", "lodash-es": "^4.18.1", diff --git a/packages/dbml-parse/package.json b/packages/dbml-parse/package.json index 59aa5db3a..1543bc892 100644 --- a/packages/dbml-parse/package.json +++ b/packages/dbml-parse/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/parse", - "version": "10.1.0", + "version": "10.1.1", "description": "DBML parser v2", "author": "Holistics ", "license": "Apache-2.0", From 2e6bb00478f59c4e3aa73a2de222f046ad1dabb6 Mon Sep 17 00:00:00 2001 From: Huy-DNA Date: Thu, 13 Aug 2026 14:49:48 +0700 Subject: [PATCH 3/5] Revert "v10.1.1" This reverts commit 8593ad45ef99440089b2ab382383b8c83a82c444. --- dbml-playground/package.json | 6 +++--- lerna.json | 2 +- packages/dbml-cli/package.json | 8 ++++---- packages/dbml-connector/package.json | 2 +- packages/dbml-core/package.json | 4 ++-- packages/dbml-parse/package.json | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dbml-playground/package.json b/dbml-playground/package.json index 8fb652362..f31c602c2 100644 --- a/dbml-playground/package.json +++ b/dbml-playground/package.json @@ -1,6 +1,6 @@ { "name": "@dbml/playground", - "version": "10.1.1", + "version": "10.1.0", "description": "Interactive playground for debugging and visualizing the DBML parser pipeline", "author": "Holistics ", "license": "Apache-2.0", @@ -26,8 +26,8 @@ "format": "prettier --write src/" }, "dependencies": { - "@dbml/core": "^10.1.1", - "@dbml/parse": "^10.1.1", + "@dbml/core": "^10.1.0", + "@dbml/parse": "^10.1.0", "@phosphor-icons/vue": "^2.2.0", "dompurify": "^3.4.13", "floating-vue": "^5.2.2", diff --git a/lerna.json b/lerna.json index e3f6b68b4..009955d9f 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "10.1.1", + "version": "10.1.0", "npmClient": "yarn", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } diff --git a/packages/dbml-cli/package.json b/packages/dbml-cli/package.json index 81cc5ccb2..8d78df541 100644 --- a/packages/dbml-cli/package.json +++ b/packages/dbml-cli/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/cli", - "version": "10.1.1", + "version": "10.1.0", "description": "", "main": "lib/index.js", "license": "Apache-2.0", @@ -33,9 +33,9 @@ ], "dependencies": { "@babel/cli": "^7.21.0", - "@dbml/connector": "^10.1.1", - "@dbml/core": "^10.1.1", - "@dbml/parse": "^10.1.1", + "@dbml/connector": "^10.1.0", + "@dbml/core": "^10.1.0", + "@dbml/parse": "^10.1.0", "bluebird": "^3.5.5", "chalk": "^2.4.2", "commander": "^2.20.0", diff --git a/packages/dbml-connector/package.json b/packages/dbml-connector/package.json index 81cfa7804..6eb0c8bc5 100644 --- a/packages/dbml-connector/package.json +++ b/packages/dbml-connector/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/connector", - "version": "10.1.1", + "version": "10.1.0", "description": "This package was created to fetch the schema JSON from many kind of databases.", "author": "huy.phung.sw@gmail.com", "license": "MIT", diff --git a/packages/dbml-core/package.json b/packages/dbml-core/package.json index 1fcd31a3b..9d1282eed 100644 --- a/packages/dbml-core/package.json +++ b/packages/dbml-core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/core", - "version": "10.1.1", + "version": "10.1.0", "description": "> TODO: description", "author": "Holistics ", "license": "Apache-2.0", @@ -47,7 +47,7 @@ "lint:fix": "eslint --fix ." }, "dependencies": { - "@dbml/parse": "^10.1.1", + "@dbml/parse": "^10.1.0", "antlr4": "^4.13.1", "lodash": "^4.18.1", "lodash-es": "^4.18.1", diff --git a/packages/dbml-parse/package.json b/packages/dbml-parse/package.json index 1543bc892..59aa5db3a 100644 --- a/packages/dbml-parse/package.json +++ b/packages/dbml-parse/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/parse", - "version": "10.1.1", + "version": "10.1.0", "description": "DBML parser v2", "author": "Holistics ", "license": "Apache-2.0", From e0ed6604c3d42902004bcfb5b22791d3f364d759 Mon Sep 17 00:00:00 2001 From: Huy-DNA Date: Thu, 13 Aug 2026 14:50:25 +0700 Subject: [PATCH 4/5] v10.1.1-alpha.0 --- dbml-playground/package.json | 6 +++--- lerna.json | 2 +- packages/dbml-cli/package.json | 8 ++++---- packages/dbml-connector/package.json | 2 +- packages/dbml-core/package.json | 4 ++-- packages/dbml-parse/package.json | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dbml-playground/package.json b/dbml-playground/package.json index f31c602c2..d49f8444a 100644 --- a/dbml-playground/package.json +++ b/dbml-playground/package.json @@ -1,6 +1,6 @@ { "name": "@dbml/playground", - "version": "10.1.0", + "version": "10.1.1-alpha.0", "description": "Interactive playground for debugging and visualizing the DBML parser pipeline", "author": "Holistics ", "license": "Apache-2.0", @@ -26,8 +26,8 @@ "format": "prettier --write src/" }, "dependencies": { - "@dbml/core": "^10.1.0", - "@dbml/parse": "^10.1.0", + "@dbml/core": "^10.1.1-alpha.0", + "@dbml/parse": "^10.1.1-alpha.0", "@phosphor-icons/vue": "^2.2.0", "dompurify": "^3.4.13", "floating-vue": "^5.2.2", diff --git a/lerna.json b/lerna.json index 009955d9f..050cefc36 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "10.1.0", + "version": "10.1.1-alpha.0", "npmClient": "yarn", "$schema": "node_modules/lerna/schemas/lerna-schema.json" } diff --git a/packages/dbml-cli/package.json b/packages/dbml-cli/package.json index 8d78df541..e4c4f587e 100644 --- a/packages/dbml-cli/package.json +++ b/packages/dbml-cli/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/cli", - "version": "10.1.0", + "version": "10.1.1-alpha.0", "description": "", "main": "lib/index.js", "license": "Apache-2.0", @@ -33,9 +33,9 @@ ], "dependencies": { "@babel/cli": "^7.21.0", - "@dbml/connector": "^10.1.0", - "@dbml/core": "^10.1.0", - "@dbml/parse": "^10.1.0", + "@dbml/connector": "^10.1.1-alpha.0", + "@dbml/core": "^10.1.1-alpha.0", + "@dbml/parse": "^10.1.1-alpha.0", "bluebird": "^3.5.5", "chalk": "^2.4.2", "commander": "^2.20.0", diff --git a/packages/dbml-connector/package.json b/packages/dbml-connector/package.json index 6eb0c8bc5..f0726b741 100644 --- a/packages/dbml-connector/package.json +++ b/packages/dbml-connector/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/connector", - "version": "10.1.0", + "version": "10.1.1-alpha.0", "description": "This package was created to fetch the schema JSON from many kind of databases.", "author": "huy.phung.sw@gmail.com", "license": "MIT", diff --git a/packages/dbml-core/package.json b/packages/dbml-core/package.json index 9d1282eed..ee8cfdf97 100644 --- a/packages/dbml-core/package.json +++ b/packages/dbml-core/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/core", - "version": "10.1.0", + "version": "10.1.1-alpha.0", "description": "> TODO: description", "author": "Holistics ", "license": "Apache-2.0", @@ -47,7 +47,7 @@ "lint:fix": "eslint --fix ." }, "dependencies": { - "@dbml/parse": "^10.1.0", + "@dbml/parse": "^10.1.1-alpha.0", "antlr4": "^4.13.1", "lodash": "^4.18.1", "lodash-es": "^4.18.1", diff --git a/packages/dbml-parse/package.json b/packages/dbml-parse/package.json index 59aa5db3a..6b17122b9 100644 --- a/packages/dbml-parse/package.json +++ b/packages/dbml-parse/package.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/package", "name": "@dbml/parse", - "version": "10.1.0", + "version": "10.1.1-alpha.0", "description": "DBML parser v2", "author": "Holistics ", "license": "Apache-2.0", From f02ceb2313e122402567337609bdb0f2c9ce5edc Mon Sep 17 00:00:00 2001 From: NQPhuc <11730168+NQPhuc@users.noreply.github.com> Date: Fri, 14 Aug 2026 12:35:49 +0700 Subject: [PATCH 5/5] misc: remove un-unsed code --- packages/dbml-core/src/index.ts | 3 - packages/dbml-core/src/transform/index.ts | 2 +- packages/dbml-core/types/index.d.ts | 3 - packages/dbml-core/types/transform/index.d.ts | 7 +- .../src/compiler/queries/transform/index.ts | 2 - .../src/compiler/queries/transform/syncDep.ts | 195 +----------------- packages/dbml-parse/src/index.ts | 4 +- 7 files changed, 7 insertions(+), 209 deletions(-) diff --git a/packages/dbml-core/src/index.ts b/packages/dbml-core/src/index.ts index 74a2e0ca2..f35c447f9 100644 --- a/packages/dbml-core/src/index.ts +++ b/packages/dbml-core/src/index.ts @@ -10,7 +10,6 @@ import { syncDiagramView, findDiagramViewBlocks, syncDep, - findDepBlocks, } from './transform'; import { VERSION } from './utils/version'; @@ -23,7 +22,6 @@ export { syncDiagramView, findDiagramViewBlocks, syncDep, - findDepBlocks, ModelExporter, CompilerError, Parser, @@ -80,7 +78,6 @@ export type { DepSyncOperation, DepSyncEdge, DepEndpointRef, - DepBlock, TextEdit, ElementIdentifier, SchemaIdentifier, diff --git a/packages/dbml-core/src/transform/index.ts b/packages/dbml-core/src/transform/index.ts index a49de2976..a47ae7118 100644 --- a/packages/dbml-core/src/transform/index.ts +++ b/packages/dbml-core/src/transform/index.ts @@ -4,7 +4,7 @@ import type { DepSyncOperation, ElementIdentifier, TableIdentifier, } from '@dbml/parse'; -export { findDiagramViewBlocks, findDepBlocks } from '@dbml/parse'; +export { findDiagramViewBlocks } from '@dbml/parse'; export function renameTable (oldName: string | TableIdentifier, newName: string | TableIdentifier, dbmlCode: string): string { const layout = new MemoryProjectLayout(); diff --git a/packages/dbml-core/types/index.d.ts b/packages/dbml-core/types/index.d.ts index b73db3be8..a2d8cd758 100644 --- a/packages/dbml-core/types/index.d.ts +++ b/packages/dbml-core/types/index.d.ts @@ -9,7 +9,6 @@ import { syncDiagramView, findDiagramViewBlocks, syncDep, - findDepBlocks, } from './transform'; export { renameTable, @@ -18,7 +17,6 @@ export { syncDiagramView, findDiagramViewBlocks, syncDep, - findDepBlocks, importer, exporter, ModelExporter, @@ -78,7 +76,6 @@ export type { DepSyncOperation, DepSyncEdge, DepEndpointRef, - DepBlock, TextEdit, ElementIdentifier, SchemaIdentifier, diff --git a/packages/dbml-core/types/transform/index.d.ts b/packages/dbml-core/types/transform/index.d.ts index c271ac6c9..2dae35401 100644 --- a/packages/dbml-core/types/transform/index.d.ts +++ b/packages/dbml-core/types/transform/index.d.ts @@ -1,6 +1,6 @@ import type { DiagramViewSyncOperation, DiagramViewBlock, TextEdit, - DepSyncOperation, DepBlock, ElementIdentifier, TableIdentifier, + DepSyncOperation, ElementIdentifier, TableIdentifier, } from '@dbml/parse'; export function renameTable( @@ -36,9 +36,4 @@ export function findDiagramViewBlocks( export function syncDep( dbmlCode: string, operations: DepSyncOperation[], - blocks?: DepBlock[], ): { newDbml: string; edits: TextEdit[] }; - -export function findDepBlocks( - dbmlCode: string, -): DepBlock[]; diff --git a/packages/dbml-parse/src/compiler/queries/transform/index.ts b/packages/dbml-parse/src/compiler/queries/transform/index.ts index 6d726fc4d..3eb086342 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/index.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/index.ts @@ -8,12 +8,10 @@ export { } from './syncDiagramView'; export { syncDep, - findDepBlocks, generateDepBlock, type DepSyncOperation, type DepSyncEdge, type DepEndpointRef, - type DepBlock, } from './syncDep'; export { applyTextEdits, type TextEdit } from './applyTextEdits'; export type { diff --git a/packages/dbml-parse/src/compiler/queries/transform/syncDep.ts b/packages/dbml-parse/src/compiler/queries/transform/syncDep.ts index ada917738..1166b2d8d 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/syncDep.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/syncDep.ts @@ -14,28 +14,15 @@ * produces a duplicate `Dep`. `remove` on an edge with no block is a no-op. */ -import { DEFAULT_ENTRY } from '@/constants'; -import Lexer from '@/core/lexer/lexer'; -import Parser from '@/core/parser/parser'; import type { Filepath } from '@/core/types/filepath'; -import { ElementKind, SettingName } from '@/core/types/keywords'; -import { DEP_DOWNSTREAM, DEP_UPSTREAM } from '@/core/types/schemaJson'; -import { - ElementDeclarationNode, - FunctionApplicationNode, - InfixExpressionNode, - ListExpressionNode, - PrefixExpressionNode, - ProgramNode, - SyntaxNodeIdGenerator, -} from '@/core/types/nodes'; -import { destructureComplexVariable, extractSettingName } from '@/core/utils/expression'; +import { SettingName } from '@/core/types/keywords'; +import { ElementDeclarationNode } from '@/core/types/nodes'; import type Compiler from '../../index'; import { formatEndpoint, findDepDefinition } from './utils'; import type { DepDefinition } from './utils'; import { TextEdit, applyTextEdits } from './applyTextEdits'; import { updateNoteEdit, removeNoteEdit, addNoteEdit } from '@/core/utils/note'; -import { updateSettingEdit, removeSettingEdit } from '@/core/utils/setting'; +import { updateSettingEdit } from '@/core/utils/setting'; // Types @@ -60,19 +47,6 @@ export interface DepSyncOperation { note?: string | null; } -export interface DepBlock { - startIndex: number; - endIndex: number; - declaration: ElementDeclarationNode; - edges: DepSyncEdge[]; -} - -export interface InlineDep { - edge: DepSyncEdge; - fullStart: number; - fullEnd: number; -} - /** * Synchronizes `Dep` blocks in the DBML source at `filepath`. * Applies create/update/remove operations and returns the rewritten source. @@ -97,31 +71,6 @@ export function syncDep ( return { newDbml, edits: allEdits }; } -/** - * Returns every standalone `Dep` block in `source`. - * On lex/parse error returns [] (mirrors findDiagramViewBlocks). - */ -export function findDepBlocks (source: string): DepBlock[] { - const lexerResult = new Lexer(source, DEFAULT_ENTRY).lex(); - if (lexerResult.getErrors().length > 0) return []; - - const ast = new Parser(source, lexerResult.getValue(), new SyntaxNodeIdGenerator(), DEFAULT_ENTRY).parse(); - if (ast.getErrors().length > 0) return []; - - return depBlocksFromProgram(ast.getValue().ast); -} - -/** Returns every inline `[dep: -> target]` setting on columns in `source`. */ -export function findInlineDeps (source: string): InlineDep[] { - const lexerResult = new Lexer(source, DEFAULT_ENTRY).lex(); - if (lexerResult.getErrors().length > 0) return []; - - const ast = new Parser(source, lexerResult.getValue(), new SyntaxNodeIdGenerator(), DEFAULT_ENTRY).parse(); - if (ast.getErrors().length > 0) return []; - - return inlineDepsFromProgram(source, ast.getValue().ast); -} - /** Emit a `Dep` block string for one edge with a `[color]` setting. */ export function generateDepBlock (edge: DepSyncEdge, color: string): string { const up = formatEndpoint(edge.upstream); @@ -129,144 +78,6 @@ export function generateDepBlock (edge: DepSyncEdge, color: string): string { return `Dep [color: ${color}] {\n ${up} -> ${down}\n}`; } -/** Extract `DepBlock[]` from an already-parsed program AST. */ -export function depBlocksFromProgram (program: ProgramNode): DepBlock[] { - const blocks: DepBlock[] = []; - - for (const element of program.declarations) { - if (!(element instanceof ElementDeclarationNode) || !element.isKind(ElementKind.Dep)) continue; - - const edges: DepSyncEdge[] = []; - - const body = element.body; - if (body instanceof FunctionApplicationNode) { - if (body.callee instanceof InfixExpressionNode) { - const edge = edgeFromInfix(body.callee); - if (edge) edges.push(edge); - } - } else if (body) { - for (const field of body.body) { - if (field instanceof FunctionApplicationNode && field.callee instanceof InfixExpressionNode) { - const edge = edgeFromInfix(field.callee); - if (edge) edges.push(edge); - } - } - } - - blocks.push({ - startIndex: element.start, - endIndex: element.end, - declaration: element, - edges, - }); - } - - return blocks; -} - -/** Extract `InlineDep[]` from an already-parsed program AST. */ -export function inlineDepsFromProgram (source: string, program: ProgramNode): InlineDep[] { - const result: InlineDep[] = []; - - for (const element of program.declarations) { - if (!(element instanceof ElementDeclarationNode) || !element.isKind(ElementKind.Table)) continue; - const tableFragments = element.name ? destructureComplexVariable(element.name) ?? [] : []; - if (tableFragments.length === 0) continue; - - const tableName = tableFragments[tableFragments.length - 1]; - const schemaName = tableFragments.length > 1 ? tableFragments[tableFragments.length - 2] : undefined; - - const body = element.body; - if (!body || body instanceof FunctionApplicationNode) continue; - for (const field of body.body) { - if (!(field instanceof FunctionApplicationNode) || !field.callee) continue; - const columnName = destructureComplexVariable(field.callee)?.at(-1); - if (!columnName) continue; - - const host: DepEndpointRef = { - schemaName, - tableName, - fieldNames: [ - columnName, - ], - }; - const edit = removeSettingEdit(field, SettingName.Dep, source); - if (!edit) continue; - for (const dep of extractInlineDepEdges(field, host)) { - result.push({ edge: dep, fullStart: edit.start, fullEnd: edit.end }); - } - } - } - - return result; -} - -// Private helpers - -/** Parse an `a -> b` / `a <- b` infix node into a DepSyncEdge. */ -function edgeFromInfix (infix: InfixExpressionNode): DepSyncEdge | undefined { - const op = infix.op?.value; - if (op !== DEP_DOWNSTREAM && op !== DEP_UPSTREAM) return undefined; - const left = infix.leftExpression; - const right = infix.rightExpression; - if (!left || !right) return undefined; - - const upstreamNode = op === DEP_UPSTREAM ? right : left; - const downstreamNode = op === DEP_UPSTREAM ? left : right; - - const upFragments = destructureComplexVariable(upstreamNode); - const downFragments = destructureComplexVariable(downstreamNode); - if (!upFragments || !downFragments) return undefined; - - const hasFields = upFragments.length > 1 && downFragments.length > 1; - const upstream = fragmentsToEndpoint(upFragments, hasFields); - const downstream = fragmentsToEndpoint(downFragments, hasFields); - if (!upstream || !downstream) return undefined; - return { upstream, downstream }; -} - -/** - * Split complex-variable fragments into schema/table/fields. - * `hasFields` resolves the `a.b` ambiguity (schema.table vs table.field). - */ -function fragmentsToEndpoint (fragments: string[], hasFields: boolean): DepEndpointRef | undefined { - if (fragments.length === 0) return undefined; - if (!hasFields) { - const tableName = fragments[fragments.length - 1]; - const schemaName = fragments.length > 1 ? fragments[fragments.length - 2] : null; - return { schemaName, tableName, fieldNames: [] }; - } - const fieldNames = [ - fragments[fragments.length - 1], - ]; - const tableName = fragments[fragments.length - 2]; - const schemaName = fragments.length > 2 ? fragments[fragments.length - 3] : null; - return { schemaName, tableName, fieldNames }; -} - -/** Extract dep edges from inline `[dep: -> target]` settings on a column. */ -function extractInlineDepEdges (field: FunctionApplicationNode, host: DepEndpointRef): DepSyncEdge[] { - const list = field.args.find((a) => a instanceof ListExpressionNode) as ListExpressionNode | undefined; - if (!list) return []; - - const edges: DepSyncEdge[] = []; - for (const attr of list.elementList) { - if (extractSettingName(attr) !== SettingName.Dep) continue; - const value = attr.value; - if (!(value instanceof PrefixExpressionNode) || !value.expression) continue; - const dir = value.op?.value; - if (dir !== DEP_DOWNSTREAM && dir !== DEP_UPSTREAM) continue; - const targetFragments = destructureComplexVariable(value.expression); - if (!targetFragments) continue; - const target = fragmentsToEndpoint(targetFragments, targetFragments.length > 1); - if (!target) continue; - edges.push(dir === DEP_DOWNSTREAM - ? { upstream: host, downstream: target } - : { upstream: target, downstream: host }); - } - return edges; -} - /** Dispatch a single sync operation to the appropriate edit strategy. */ function applyOperation (compiler: Compiler, filepath: Filepath, dbml: string, operation: DepSyncOperation): TextEdit[] { const definition = findDepDefinition(compiler, filepath, operation.edge); diff --git a/packages/dbml-parse/src/index.ts b/packages/dbml-parse/src/index.ts index 2e9c9e372..ca5e50b49 100644 --- a/packages/dbml-parse/src/index.ts +++ b/packages/dbml-parse/src/index.ts @@ -115,11 +115,11 @@ export type { TextEdit, } from '@/compiler/queries/transform'; -export { findDiagramViewBlocks, findDepBlocks } from '@/compiler/queries/transform'; +export { findDiagramViewBlocks } from '@/compiler/queries/transform'; // Dep transform types export type { - DepSyncOperation, DepSyncEdge, DepEndpointRef, DepBlock, + DepSyncOperation, DepSyncEdge, DepEndpointRef, } from '@/compiler/queries/transform'; // Element identifier types