From 36bcf340f1375f3589e471b6501bfcc688ebe5b1 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Thu, 12 Mar 2026 17:16:08 +0700 Subject: [PATCH 01/31] feat(dbml): add DiagramView ElementKind Co-Authored-By: Claude Opus 4.6 --- packages/dbml-parse/src/core/analyzer/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dbml-parse/src/core/analyzer/types.ts b/packages/dbml-parse/src/core/analyzer/types.ts index 587dbbdcc..98dbd5397 100644 --- a/packages/dbml-parse/src/core/analyzer/types.ts +++ b/packages/dbml-parse/src/core/analyzer/types.ts @@ -9,6 +9,7 @@ export enum ElementKind { TablePartial = 'tablepartial', Check = 'checks', Records = 'records', + DiagramView = 'diagramview', } export enum SettingName { From a716e4c97946c030f983e673f4d3d0357fe03cbc Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Thu, 12 Mar 2026 17:49:07 +0700 Subject: [PATCH 02/31] feat(dbml): add DiagramViewSymbol and DiagramViewFieldSymbol classes Add two new symbol classes for DiagramView support: - DiagramViewSymbol: Represents a DiagramView block with its own symbol table - DiagramViewFieldSymbol: Represents DiagramView fields (table/note/group/schema references) Follows the same pattern as TableGroupSymbol and TableGroupFieldSymbol. Co-Authored-By: Claude Opus 4.6 --- .../src/core/analyzer/symbol/symbols.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/packages/dbml-parse/src/core/analyzer/symbol/symbols.ts b/packages/dbml-parse/src/core/analyzer/symbol/symbols.ts index 8ba24822a..9f87b2d80 100644 --- a/packages/dbml-parse/src/core/analyzer/symbol/symbols.ts +++ b/packages/dbml-parse/src/core/analyzer/symbol/symbols.ts @@ -142,6 +142,35 @@ export class TableGroupFieldSymbol implements NodeSymbol { } } +// A symbol for a DiagramView block +export class DiagramViewSymbol implements NodeSymbol { + id: NodeSymbolId; + symbolTable: SymbolTable; + declaration: SyntaxNode; + references: SyntaxNode[] = []; + + constructor( + { symbolTable, declaration }: { symbolTable: SymbolTable; declaration: SyntaxNode }, + id: NodeSymbolId, + ) { + this.id = id; + this.symbolTable = symbolTable; + this.declaration = declaration; + } +} + +// A symbol for a DiagramView field (table/note/group/schema reference) +export class DiagramViewFieldSymbol implements NodeSymbol { + id: NodeSymbolId; + declaration: SyntaxNode; + references: SyntaxNode[] = []; + + constructor({ declaration }: { declaration: SyntaxNode }, id: NodeSymbolId) { + this.id = id; + this.declaration = declaration; + } +} + // A symbol for a table partial, contains the table partial's symbol table // which is used to hold all the column symbols of the table partial export class TablePartialSymbol implements NodeSymbol { From 7dafc06f0c05a03554c64f07bb2cc7195a269f22 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Thu, 12 Mar 2026 17:55:07 +0700 Subject: [PATCH 03/31] feat(dbml): add DiagramView symbol index functions - Add DiagramView and DiagramViewField to SymbolKind enum - Add createDiagramViewSymbolIndex and createDiagramViewFieldSymbolIndex - Update createNodeSymbolIndex to handle new symbol kinds Co-Authored-By: Claude Opus 4.6 --- .../src/core/analyzer/symbol/symbolIndex.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/dbml-parse/src/core/analyzer/symbol/symbolIndex.ts b/packages/dbml-parse/src/core/analyzer/symbol/symbolIndex.ts index 35f191e16..6ae7d4082 100644 --- a/packages/dbml-parse/src/core/analyzer/symbol/symbolIndex.ts +++ b/packages/dbml-parse/src/core/analyzer/symbol/symbolIndex.ts @@ -14,6 +14,8 @@ export enum SymbolKind { Note = 'Note', TablePartial = 'TablePartial', PartialInjection = 'PartialInjection', + DiagramView = 'DiagramView', + DiagramViewField = 'DiagramView field', } export function createSchemaSymbolIndex (key: string): NodeSymbolIndex { @@ -56,6 +58,14 @@ export function createPartialInjectionSymbolIndex (key: string): NodeSymbolIndex return `${SymbolKind.PartialInjection}:${key}`; } +export function createDiagramViewSymbolIndex (key: string): NodeSymbolIndex { + return `${SymbolKind.DiagramView}:${key}`; +} + +export function createDiagramViewFieldSymbolIndex (key: string): NodeSymbolIndex { + return `${SymbolKind.DiagramViewField}:${key}`; +} + export function createNodeSymbolIndex (key: string, symbolKind: SymbolKind): NodeSymbolIndex { switch (symbolKind) { case SymbolKind.Column: @@ -76,6 +86,10 @@ export function createNodeSymbolIndex (key: string, symbolKind: SymbolKind): Nod return createTablePartialSymbolIndex(key); case SymbolKind.PartialInjection: return createPartialInjectionSymbolIndex(key); + case SymbolKind.DiagramView: + return createDiagramViewSymbolIndex(key); + case SymbolKind.DiagramViewField: + return createDiagramViewFieldSymbolIndex(key); default: throw new Error('Unreachable'); } From 0c86144d11a81ff7fdd5b8afc82bb6b2d0866dc0 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Thu, 12 Mar 2026 18:08:03 +0700 Subject: [PATCH 04/31] feat(dbml): add DiagramView validator - Add DiagramViewValidator class with validation for: - Name requirements (required, single identifier) - Top-level context validation - Allowed sub-blocks (Tables, Notes, TableGroups, Schemas) - Wildcard usage warning - Duplicate field detection - Add error codes for DiagramView validation - Register validator in pickValidator switch Co-Authored-By: Claude Opus 4.6 --- .../elementValidators/diagramView.ts | 248 ++++++++++++++++++ .../src/core/analyzer/validator/utils.ts | 3 + packages/dbml-parse/src/core/errors.ts | 5 + 3 files changed, 256 insertions(+) create mode 100644 packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts new file mode 100644 index 000000000..bc15db8db --- /dev/null +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts @@ -0,0 +1,248 @@ +import { partition } from 'lodash-es'; +import { CompileError, CompileErrorCode, CompileWarning } from '@/core/errors'; +import { + isSimpleName, pickValidator, +} from '@/core/analyzer/validator/utils'; +import { registerSchemaStack, aggregateSettingList } from '@/core/analyzer/validator/utils'; +import { ElementValidator } from '@/core/analyzer/validator/types'; +import SymbolTable from '@/core/analyzer/symbol/symbolTable'; +import { SyntaxToken } from '@/core/lexer/tokens'; +import { + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, SyntaxNode, +} from '@/core/parser/nodes'; +import SymbolFactory from '@/core/analyzer/symbol/factory'; +import { createDiagramViewFieldSymbolIndex, createDiagramViewSymbolIndex } from '@/core/analyzer/symbol/symbolIndex'; +import { destructureComplexVariable, extractVarNameFromPrimaryVariable } from '@/core/analyzer/utils'; +import { DiagramViewFieldSymbol, DiagramViewSymbol } from '@/core/analyzer/symbol/symbols'; +import { isExpressionAVariableNode } from '@/core/parser/utils'; + +export default class DiagramViewValidator implements ElementValidator { + private declarationNode: ElementDeclarationNode & { type: SyntaxToken }; + private publicSymbolTable: SymbolTable; + private symbolFactory: SymbolFactory; + + constructor (declarationNode: ElementDeclarationNode & { type: SyntaxToken }, publicSymbolTable: SymbolTable, symbolFactory: SymbolFactory) { + this.declarationNode = declarationNode; + this.publicSymbolTable = publicSymbolTable; + this.symbolFactory = symbolFactory; + } + + validate (): (CompileError | CompileWarning)[] { + return [ + ...this.validateContext(), + ...this.validateName(this.declarationNode.name), + ...this.validateAlias(this.declarationNode.alias), + ...this.validateSettingList(this.declarationNode.attributeList), + ...this.registerElement(), + ...this.validateBody(this.declarationNode.body), + ]; + } + + private validateContext (): CompileError[] { + if (this.declarationNode.parent instanceof ElementDeclarationNode) { + return [new CompileError( + CompileErrorCode.INVALID_DIAGRAMVIEW_CONTEXT, + 'DiagramView must appear top-level', + this.declarationNode, + )]; + } + return []; + } + + private validateName (nameNode?: SyntaxNode): CompileError[] { + if (!nameNode) { + return [new CompileError( + CompileErrorCode.NAME_NOT_FOUND, + 'A DiagramView must have a name', + this.declarationNode, + )]; + } + if (!isSimpleName(nameNode)) { + return [new CompileError( + CompileErrorCode.INVALID_NAME, + 'A DiagramView name must be a single identifier', + nameNode, + )]; + } + return []; + } + + private validateAlias (aliasNode?: SyntaxNode): CompileError[] { + if (aliasNode) { + return [new CompileError( + CompileErrorCode.UNEXPECTED_ALIAS, + 'A DiagramView shouldn\'t have an alias', + aliasNode, + )]; + } + + return []; + } + + registerElement (): CompileError[] { + const { name } = this.declarationNode; + this.declarationNode.symbol = this.symbolFactory.create(DiagramViewSymbol, { declaration: this.declarationNode, symbolTable: new SymbolTable() }); + const maybeNameFragments = destructureComplexVariable(name); + if (maybeNameFragments.isOk()) { + const nameFragments = maybeNameFragments.unwrap(); + const diagramViewName = nameFragments.pop()!; + const symbolTable = registerSchemaStack(nameFragments, this.publicSymbolTable, this.symbolFactory); + const diagramViewId = createDiagramViewSymbolIndex(diagramViewName); + if (symbolTable.has(diagramViewId)) { + return [new CompileError(CompileErrorCode.DUPLICATE_DIAGRAMVIEW_NAME, `DiagramView name '${diagramViewName}' already exists`, name!)]; + } + symbolTable.set(diagramViewId, this.declarationNode.symbol!); + } + + return []; + } + + private validateSettingList (settingList?: ListExpressionNode): CompileError[] { + const aggReport = aggregateSettingList(settingList); + const errors: CompileError[] = aggReport.getErrors(); + const settingMap = aggReport.getValue(); + + // DiagramView doesn't have any supported settings yet + for (const name of Object.keys(settingMap)) { + errors.push(...settingMap[name].map((attr) => new CompileError( + CompileErrorCode.UNEXPECTED_SETTINGS, + `Unknown '${name}' setting for DiagramView`, + attr, + ))); + } + return errors; + } + + validateBody (body?: FunctionApplicationNode | BlockExpressionNode): (CompileError | CompileWarning)[] { + if (!body) return []; + + if (body instanceof FunctionApplicationNode) { + return [new CompileError( + CompileErrorCode.UNEXPECTED_SIMPLE_BODY, + 'A DiagramView\'s body must be a block', + body, + )]; + } + + const [fields, subs] = partition(body.body, (e) => e instanceof FunctionApplicationNode); + return [ + ...this.validateFields(fields as FunctionApplicationNode[]), + ...this.validateSubElements(subs as ElementDeclarationNode[]), + ]; + } + + validateFields (fields: FunctionApplicationNode[]): (CompileError | CompileWarning)[] { + return fields.flatMap((field) => { + const errors: (CompileError | CompileWarning)[] = []; + // Fields at the top level of DiagramView are not allowed + errors.push(new CompileError( + CompileErrorCode.INVALID_DIAGRAMVIEW_FIELD, + 'Fields are not allowed at DiagramView level. Use Tables, Notes, TableGroups, or Schemas blocks instead.', + field, + )); + return errors; + }); + } + + private validateSubElements (subs: ElementDeclarationNode[]): (CompileError | CompileWarning)[] { + const errors: (CompileError | CompileWarning)[] = []; + + // Validate allowed sub-blocks: Tables, Notes, TableGroups, Schemas + const allowedBlocks = ['tables', 'notes', 'tablegroups', 'schemas']; + + for (const sub of subs) { + sub.parent = this.declarationNode; + if (!sub.type) { + continue; + } + + const blockType = sub.type.value.toLowerCase(); + if (!allowedBlocks.includes(blockType)) { + errors.push(new CompileError( + CompileErrorCode.INVALID_DIAGRAMVIEW_FIELD, + `Unknown block type "${sub.type.value}" in DiagramView. Allowed: Tables, Notes, TableGroups, Schemas`, + sub, + )); + continue; + } + + // Validate the sub-block body + errors.push(...this.validateSubBlock(sub)); + + // Register fields in the sub-block + errors.push(...this.registerSubBlockFields(sub)); + } + + return errors; + } + + private validateSubBlock (sub: ElementDeclarationNode): (CompileError | CompileWarning)[] { + const errors: (CompileError | CompileWarning)[] = []; + + if (!sub.body || !(sub.body instanceof BlockExpressionNode)) { + return errors; + } + + const body = sub.body as BlockExpressionNode; + + // Check for * combined with specific items (warning) + const hasWildcard = body.body.some( + (e) => e instanceof FunctionApplicationNode && e.callee?.type === 'WildcardExpression', + ); + const hasSpecificItems = body.body.some( + (e) => e instanceof FunctionApplicationNode && e.callee?.type !== 'WildcardExpression', + ); + + if (hasWildcard && hasSpecificItems) { + errors.push(new CompileWarning( + CompileErrorCode.INVALID_DIAGRAMVIEW_FIELD, + `Wildcard (*) combined with specific items in ${sub.type?.value} block. Specific items will be ignored.`, + sub, + )); + } + + return errors; + } + + registerSubBlockFields (sub: ElementDeclarationNode): CompileError[] { + const errors: CompileError[] = []; + + if (!sub.body || !(sub.body instanceof BlockExpressionNode)) { + return errors; + } + + const body = sub.body as BlockExpressionNode; + const fields = body.body.filter((e) => e instanceof FunctionApplicationNode) as FunctionApplicationNode[]; + + for (const field of fields) { + if (field.callee && isExpressionAVariableNode(field.callee)) { + const fieldName = extractVarNameFromPrimaryVariable(field.callee).unwrap(); + const fieldId = createDiagramViewFieldSymbolIndex(fieldName); + + const fieldSymbol = this.symbolFactory.create(DiagramViewFieldSymbol, { declaration: field }); + field.symbol = fieldSymbol; + + const symbolTable = this.declarationNode.symbol!.symbolTable!; + if (symbolTable.has(fieldId)) { + const symbol = symbolTable.get(fieldId); + errors.push( + new CompileError(CompileErrorCode.DUPLICATE_DIAGRAMVIEW_FIELD, `${fieldName} already exists in DiagramView`, field), + new CompileError(CompileErrorCode.DUPLICATE_DIAGRAMVIEW_FIELD, `${fieldName} already exists in DiagramView`, symbol!.declaration!), + ); + } else { + symbolTable.set(fieldId, fieldSymbol); + } + } + + if (field.args.length > 0) { + errors.push(...field.args.map((arg) => new CompileError( + CompileErrorCode.INVALID_DIAGRAMVIEW_FIELD, + 'DiagramView field should only have a single name', + arg, + ))); + } + } + + return errors; + } +} diff --git a/packages/dbml-parse/src/core/analyzer/validator/utils.ts b/packages/dbml-parse/src/core/analyzer/validator/utils.ts index 05ead97d4..a71f6a2fb 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/utils.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/utils.ts @@ -39,6 +39,7 @@ import { ElementKind } from '@/core/analyzer/types'; import TablePartialValidator from './elementValidators/tablePartial'; import ChecksValidator from './elementValidators/checks'; import RecordsValidator from './elementValidators/records'; +import DiagramViewValidator from './elementValidators/diagramView'; export function pickValidator (element: ElementDeclarationNode & { type: SyntaxToken }) { switch (element.type.value.toLowerCase() as ElementKind) { @@ -62,6 +63,8 @@ export function pickValidator (element: ElementDeclarationNode & { type: SyntaxT return ChecksValidator; case ElementKind.Records: return RecordsValidator; + case ElementKind.DiagramView: + return DiagramViewValidator; default: return CustomValidator; } diff --git a/packages/dbml-parse/src/core/errors.ts b/packages/dbml-parse/src/core/errors.ts index d453b7e71..e4c1df837 100644 --- a/packages/dbml-parse/src/core/errors.ts +++ b/packages/dbml-parse/src/core/errors.ts @@ -115,6 +115,11 @@ export enum CompileErrorCode { DUPLICATE_COLUMN_REFERENCES_IN_RECORDS, DUPLICATE_RECORDS_FOR_TABLE, + INVALID_DIAGRAMVIEW_CONTEXT, + DUPLICATE_DIAGRAMVIEW_NAME, + INVALID_DIAGRAMVIEW_FIELD, + DUPLICATE_DIAGRAMVIEW_FIELD, + BINDING_ERROR = 4000, UNSUPPORTED = 5000, From f83fa33e7386d8d5cf3482e88e27790f3cf69c93 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Thu, 12 Mar 2026 18:19:48 +0700 Subject: [PATCH 05/31] feat(dbml): add DiagramView binder and interpreter - Add DiagramViewBinder for binding references in DiagramView blocks - Add DiagramViewInterpreter for interpreting DiagramView syntax - Add FilterConfig and DiagramView types to interpreter types - Register binder in pickBinder function - Register interpreter in main interpreter - Update Database type to include diagramViews array - Update snapshots Co-Authored-By: Claude Opus 4.6 --- .../interpreter/output/array_type.out.json | 3 +- .../interpreter/output/checks.out.json | 3 +- .../output/column_caller_type.out.json | 3 +- .../interpreter/output/comment.out.json | 3 +- .../output/default_tables.out.json | 3 +- .../enum_as_default_column_value.out.json | 3 +- .../interpreter/output/enum_tables.out.json | 3 +- .../output/general_schema.out.json | 3 +- .../output/header_color_tables.out.json | 3 +- .../output/index_table_partial.out.json | 3 +- .../interpreter/output/index_tables.out.json | 3 +- .../interpreter/output/multi_notes.out.json | 3 +- .../output/multiline_string.out.json | 3 +- .../output/negative_number.out.json | 3 +- .../output/note_normalize.out.json | 3 +- ...te_normalize_with_top_empty_lines.out.json | 3 +- .../output/old_undocumented_syntax.out.json | 3 +- .../interpreter/output/primary_key.out.json | 3 +- .../interpreter/output/project.out.json | 3 +- .../ref_name_and_color_setting.out.json | 3 +- .../interpreter/output/ref_settings.out.json | 3 +- .../output/referential_actions.out.json | 3 +- .../interpreter/output/sticky_notes.out.json | 3 +- .../interpreter/output/table_group.out.json | 3 +- .../output/table_group_element.out.json | 3 +- .../output/table_group_settings.out.json | 3 +- .../interpreter/output/table_partial.out.json | 3 +- .../output/table_settings.out.json | 3 +- ...tablepartial_causing_circular_ref.out.json | 3 +- .../binder/elementBinder/diagramView.ts | 191 ++++++++++++++++++ .../src/core/analyzer/binder/utils.ts | 3 + .../elementInterpreter/diagramView.ts | 150 ++++++++++++++ .../src/core/interpreter/interpreter.ts | 5 + .../dbml-parse/src/core/interpreter/types.ts | 22 ++ 34 files changed, 429 insertions(+), 29 deletions(-) create mode 100644 packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts create mode 100644 packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/array_type.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/array_type.out.json index 1f3ca4355..13c3cfd1b 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/array_type.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/array_type.out.json @@ -151,5 +151,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/checks.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/checks.out.json index 43db72b1a..bb853c6a7 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/checks.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/checks.out.json @@ -362,5 +362,6 @@ ] } ], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/column_caller_type.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/column_caller_type.out.json index 26a931eae..049487e0e 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/column_caller_type.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/column_caller_type.out.json @@ -146,5 +146,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/comment.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/comment.out.json index 4ef049648..116f5b6e6 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/comment.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/comment.out.json @@ -402,5 +402,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/default_tables.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/default_tables.out.json index ae9a21ec6..0d4af5bc2 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/default_tables.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/default_tables.out.json @@ -428,5 +428,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/enum_as_default_column_value.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/enum_as_default_column_value.out.json index e7fbe1b13..0a58ef945 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/enum_as_default_column_value.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/enum_as_default_column_value.out.json @@ -369,5 +369,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/enum_tables.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/enum_tables.out.json index b767ed50a..238fc8d6b 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/enum_tables.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/enum_tables.out.json @@ -419,5 +419,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/general_schema.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/general_schema.out.json index 303be6c61..09dd98159 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/general_schema.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/general_schema.out.json @@ -1432,5 +1432,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/header_color_tables.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/header_color_tables.out.json index 690ddc2b1..ed3618b4d 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/header_color_tables.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/header_color_tables.out.json @@ -124,5 +124,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/index_table_partial.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/index_table_partial.out.json index 3634ccb7b..02145aca9 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/index_table_partial.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/index_table_partial.out.json @@ -555,5 +555,6 @@ "checks": [] } ], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/index_tables.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/index_tables.out.json index 050d6e8ae..a8ba8ecda 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/index_tables.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/index_tables.out.json @@ -518,5 +518,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/multi_notes.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/multi_notes.out.json index 3fea92937..d173e0d09 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/multi_notes.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/multi_notes.out.json @@ -721,5 +721,6 @@ "database_type": "PostgreSQL" }, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/multiline_string.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/multiline_string.out.json index c9a52742d..a1f3945e6 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/multiline_string.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/multiline_string.out.json @@ -71,5 +71,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/negative_number.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/negative_number.out.json index 347785c42..173130c5f 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/negative_number.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/negative_number.out.json @@ -287,5 +287,6 @@ "checks": [] } ], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/note_normalize.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/note_normalize.out.json index 965130ff0..0dcf41dd1 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/note_normalize.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/note_normalize.out.json @@ -615,5 +615,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/note_normalize_with_top_empty_lines.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/note_normalize_with_top_empty_lines.out.json index 1341f522a..50db274b6 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/note_normalize_with_top_empty_lines.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/note_normalize_with_top_empty_lines.out.json @@ -615,5 +615,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/old_undocumented_syntax.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/old_undocumented_syntax.out.json index bb6912cc4..36ca868c7 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/old_undocumented_syntax.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/old_undocumented_syntax.out.json @@ -578,5 +578,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/primary_key.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/primary_key.out.json index 147c1ea31..66604151d 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/primary_key.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/primary_key.out.json @@ -56,5 +56,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/project.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/project.out.json index bea3fb662..169b4d11b 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/project.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/project.out.json @@ -1467,5 +1467,6 @@ "database_type": "PostgreSQL" }, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/ref_name_and_color_setting.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/ref_name_and_color_setting.out.json index 69fe64bc2..d5b9c4a83 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/ref_name_and_color_setting.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/ref_name_and_color_setting.out.json @@ -265,5 +265,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/ref_settings.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/ref_settings.out.json index 9d93d897c..5d19abbfc 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/ref_settings.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/ref_settings.out.json @@ -266,5 +266,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/referential_actions.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/referential_actions.out.json index 999e87990..53a40ca3f 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/referential_actions.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/referential_actions.out.json @@ -976,5 +976,6 @@ "checks": [] } ], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/sticky_notes.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/sticky_notes.out.json index 3fb76b5e9..b3977f30f 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/sticky_notes.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/sticky_notes.out.json @@ -116,5 +116,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_group.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_group.out.json index e095c4f08..89fc03964 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_group.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_group.out.json @@ -378,5 +378,6 @@ ], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_group_element.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_group_element.out.json index 96dccf5a2..926502697 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_group_element.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_group_element.out.json @@ -209,5 +209,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_group_settings.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_group_settings.out.json index 58c49c980..0c074b167 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_group_settings.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_group_settings.out.json @@ -95,5 +95,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_partial.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_partial.out.json index fbb749af2..e65e09c5f 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_partial.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_partial.out.json @@ -1014,5 +1014,6 @@ "checks": [] } ], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_settings.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_settings.out.json index be391fe68..43136ce64 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_settings.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/table_settings.out.json @@ -529,5 +529,6 @@ "aliases": [], "project": {}, "tablePartials": [], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/interpreter/output/tablepartial_causing_circular_ref.out.json b/packages/dbml-parse/__tests__/snapshots/interpreter/output/tablepartial_causing_circular_ref.out.json index 771244394..42a97e8f8 100644 --- a/packages/dbml-parse/__tests__/snapshots/interpreter/output/tablepartial_causing_circular_ref.out.json +++ b/packages/dbml-parse/__tests__/snapshots/interpreter/output/tablepartial_causing_circular_ref.out.json @@ -265,5 +265,6 @@ "checks": [] } ], - "records": [] + "records": [], + "diagramViews": [] } \ No newline at end of file diff --git a/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts b/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts new file mode 100644 index 000000000..b40cd73f8 --- /dev/null +++ b/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts @@ -0,0 +1,191 @@ +import { partition } from 'lodash-es'; +import { + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ProgramNode, +} from '../../../parser/nodes'; +import { ElementBinder } from '../types'; +import { SyntaxToken } from '../../../lexer/tokens'; +import { CompileError } from '../../../errors'; +import { lookupAndBindInScope, pickBinder, scanNonListNodeForBinding } from '../utils'; +import { SymbolKind } from '../../symbol/symbolIndex'; +import SymbolFactory from '../../symbol/factory'; + +export default class DiagramViewBinder implements ElementBinder { + private symbolFactory: SymbolFactory; + private declarationNode: ElementDeclarationNode & { type: SyntaxToken }; + private ast: ProgramNode; + + constructor (declarationNode: ElementDeclarationNode & { type: SyntaxToken }, ast: ProgramNode, symbolFactory: SymbolFactory) { + this.declarationNode = declarationNode; + this.ast = ast; + this.symbolFactory = symbolFactory; + } + + bind (): CompileError[] { + if (!(this.declarationNode.body instanceof BlockExpressionNode)) { + return []; + } + + return this.bindBody(this.declarationNode.body); + } + + private bindBody (body: BlockExpressionNode): CompileError[] { + const [, subs] = partition(body.body, (e) => e instanceof FunctionApplicationNode); + + return this.bindSubElements(subs as ElementDeclarationNode[]); + } + + private bindSubElements (subs: ElementDeclarationNode[]): CompileError[] { + return subs.flatMap((sub) => { + if (!sub.type) { + return []; + } + + const blockType = sub.type.value.toLowerCase(); + + // Only bind sub-blocks with body + if (!(sub.body instanceof BlockExpressionNode)) { + return []; + } + + // Bind fields based on block type + const errors: CompileError[] = []; + + switch (blockType) { + case 'tables': + errors.push(...this.bindTableReferences(sub.body)); + break; + case 'notes': + errors.push(...this.bindNoteReferences(sub.body)); + break; + case 'tablegroups': + errors.push(...this.bindTableGroupReferences(sub.body)); + break; + case 'schemas': + errors.push(...this.bindSchemaReferences(sub.body)); + break; + default: + // Unknown block type - will be caught by validator + break; + } + + return errors; + }); + } + + private bindTableReferences (body: BlockExpressionNode): CompileError[] { + const [fields] = partition(body.body, (e) => e instanceof FunctionApplicationNode); + + return (fields as FunctionApplicationNode[]).flatMap((field) => { + if (!field.callee) { + return []; + } + + // Skip wildcard + if (field.callee.type === 'WildcardExpression') { + return []; + } + + const args = [field.callee, ...field.args]; + const bindees = args.flatMap(scanNonListNodeForBinding); + + return bindees.flatMap((bindee) => { + const tableBindee = bindee.variables.pop(); + if (!tableBindee) { + return []; + } + const schemaBindees = bindee.variables; + + return lookupAndBindInScope(this.ast, [ + ...schemaBindees.map((b) => ({ node: b, kind: SymbolKind.Schema })), + { node: tableBindee, kind: SymbolKind.Table }, + ]); + }); + }); + } + + private bindNoteReferences (body: BlockExpressionNode): CompileError[] { + const [fields] = partition(body.body, (e) => e instanceof FunctionApplicationNode); + + return (fields as FunctionApplicationNode[]).flatMap((field) => { + if (!field.callee) { + return []; + } + + // Skip wildcard + if (field.callee.type === 'WildcardExpression') { + return []; + } + + const bindees = scanNonListNodeForBinding(field.callee); + + return bindees.flatMap((bindee) => { + const noteBindee = bindee.variables.pop(); + if (!noteBindee) { + return []; + } + + return lookupAndBindInScope(this.ast, [ + { node: noteBindee, kind: SymbolKind.Note }, + ]); + }); + }); + } + + private bindTableGroupReferences (body: BlockExpressionNode): CompileError[] { + const [fields] = partition(body.body, (e) => e instanceof FunctionApplicationNode); + + return (fields as FunctionApplicationNode[]).flatMap((field) => { + if (!field.callee) { + return []; + } + + // Skip wildcard + if (field.callee.type === 'WildcardExpression') { + return []; + } + + const bindees = scanNonListNodeForBinding(field.callee); + + return bindees.flatMap((bindee) => { + const tableGroupBindee = bindee.variables.pop(); + if (!tableGroupBindee) { + return []; + } + const schemaBindees = bindee.variables; + + return lookupAndBindInScope(this.ast, [ + ...schemaBindees.map((b) => ({ node: b, kind: SymbolKind.Schema })), + { node: tableGroupBindee, kind: SymbolKind.TableGroup }, + ]); + }); + }); + } + + private bindSchemaReferences (body: BlockExpressionNode): CompileError[] { + const [fields] = partition(body.body, (e) => e instanceof FunctionApplicationNode); + + return (fields as FunctionApplicationNode[]).flatMap((field) => { + if (!field.callee) { + return []; + } + + // Skip wildcard + if (field.callee.type === 'WildcardExpression') { + return []; + } + + const bindees = scanNonListNodeForBinding(field.callee); + + return bindees.flatMap((bindee) => { + const schemaBindee = bindee.variables.pop(); + if (!schemaBindee) { + return []; + } + + return lookupAndBindInScope(this.ast, [ + { node: schemaBindee, kind: SymbolKind.Schema }, + ]); + }); + }); + } +} diff --git a/packages/dbml-parse/src/core/analyzer/binder/utils.ts b/packages/dbml-parse/src/core/analyzer/binder/utils.ts index 7157c3ed3..68ef192fb 100644 --- a/packages/dbml-parse/src/core/analyzer/binder/utils.ts +++ b/packages/dbml-parse/src/core/analyzer/binder/utils.ts @@ -3,6 +3,7 @@ import { ElementDeclarationNode, InfixExpressionNode, PostfixExpressionNode, Pre import { ElementKind } from '@/core/analyzer/types'; import ChecksBinder from './elementBinder/checks'; import CustomBinder from './elementBinder/custom'; +import DiagramViewBinder from './elementBinder/diagramView'; import EnumBinder from './elementBinder/enum'; import IndexesBinder from './elementBinder/indexes'; import NoteBinder from './elementBinder/note'; @@ -41,6 +42,8 @@ export function pickBinder (element: ElementDeclarationNode & { type: SyntaxToke return ChecksBinder; case ElementKind.Records: return RecordsBinder; + case ElementKind.DiagramView: + return DiagramViewBinder; default: return CustomBinder; } diff --git a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts new file mode 100644 index 000000000..b240fdb32 --- /dev/null +++ b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts @@ -0,0 +1,150 @@ +import { partition } from 'lodash-es'; +import { destructureComplexVariable } from '@/core/analyzer/utils'; +import { CompileError } from '@/core/errors'; +import { BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, SyntaxNode } from '@/core/parser/nodes'; +import { ElementInterpreter, InterpreterDatabase, DiagramView, FilterConfig } from '@/core/interpreter/types'; +import { getTokenPosition } from '@/core/interpreter/utils'; +import { DEFAULT_SCHEMA_NAME } from '@/constants'; + +export class DiagramViewInterpreter implements ElementInterpreter { + private declarationNode: ElementDeclarationNode; + private env: InterpreterDatabase; + private diagramView: Partial; + + constructor (declarationNode: ElementDeclarationNode, env: InterpreterDatabase) { + this.declarationNode = declarationNode; + this.env = env; + this.diagramView = { + visibleEntities: { + tables: null, + stickyNotes: null, + tableGroups: null, + schemas: null, + }, + }; + } + + interpret (): CompileError[] { + const errors: CompileError[] = []; + this.diagramView.token = getTokenPosition(this.declarationNode); + + // Initialize diagramViews map if not exists + if (!this.env.diagramViews) { + this.env.diagramViews = new Map(); + } + this.env.diagramViews.set(this.declarationNode, this.diagramView as DiagramView); + + // Interpret name + if (this.declarationNode.name) { + errors.push(...this.interpretName(this.declarationNode.name)); + } + + // Interpret body + if (this.declarationNode.body instanceof BlockExpressionNode) { + errors.push(...this.interpretBody(this.declarationNode.body)); + } + + return errors; + } + + private interpretName (nameNode: SyntaxNode): CompileError[] { + const fragments = destructureComplexVariable(nameNode).unwrap_or([]); + if (fragments.length > 0) { + this.diagramView.name = fragments[fragments.length - 1]; + if (fragments.length > 1) { + this.diagramView.schemaName = fragments.slice(0, -1).join('.'); + } + } + return []; + } + + private interpretBody (body: BlockExpressionNode): CompileError[] { + // Check for shorthand { * } + if (body.body.length === 1) { + const first = body.body[0]; + if (first instanceof FunctionApplicationNode && first.callee?.type === 'WildcardExpression') { + // Show all entities + this.diagramView.visibleEntities = { + tables: [], + stickyNotes: [], + tableGroups: [], + schemas: [], + }; + return []; + } + } + + const [, subs] = partition(body.body, (e) => e instanceof FunctionApplicationNode); + + for (const sub of subs as ElementDeclarationNode[]) { + const blockType = sub.type?.value.toLowerCase(); + if (sub.body instanceof BlockExpressionNode) { + this.interpretSubBlock(sub.body, blockType); + } + } + + return []; + } + + private interpretSubBlock (body: BlockExpressionNode, blockType: string | undefined): void { + if (!blockType) return; + + // Check for wildcard + const hasWildcard = body.body.some( + (e) => e instanceof FunctionApplicationNode && e.callee?.type === 'WildcardExpression', + ); + + if (hasWildcard) { + // Show all for this entity type + switch (blockType) { + case 'tables': + this.diagramView.visibleEntities!.tables = []; + break; + case 'notes': + this.diagramView.visibleEntities!.stickyNotes = []; + break; + case 'tablegroups': + this.diagramView.visibleEntities!.tableGroups = []; + break; + case 'schemas': + this.diagramView.visibleEntities!.schemas = []; + break; + } + return; + } + + // Empty block = hide all (null is already default) + if (body.body.length === 0) { + return; + } + + // Specific items + const items: Array<{ name: string; schemaName: string }> = []; + for (const field of body.body) { + if (!(field instanceof FunctionApplicationNode)) continue; + + const fragments = destructureComplexVariable(field.callee).unwrap_or([]); + if (fragments.length === 0) continue; + + const name = fragments[fragments.length - 1]; + const schemaName = fragments.length > 1 ? fragments[0] : DEFAULT_SCHEMA_NAME; + + items.push({ name, schemaName }); + } + + switch (blockType) { + case 'tables': + this.diagramView.visibleEntities!.tables = items.length > 0 ? items : null; + break; + case 'notes': + this.diagramView.visibleEntities!.stickyNotes = items.length > 0 ? items.map((i) => ({ name: i.name })) : null; + break; + case 'tablegroups': + this.diagramView.visibleEntities!.tableGroups = items.length > 0 ? items.map((i) => ({ name: i.name })) : null; + break; + case 'schemas': + this.diagramView.visibleEntities!.schemas = items.length > 0 ? items.map((i) => ({ name: i.name })) : null; + break; + } + } +} diff --git a/packages/dbml-parse/src/core/interpreter/interpreter.ts b/packages/dbml-parse/src/core/interpreter/interpreter.ts index bd60a7b25..90a3ddffb 100644 --- a/packages/dbml-parse/src/core/interpreter/interpreter.ts +++ b/packages/dbml-parse/src/core/interpreter/interpreter.ts @@ -7,6 +7,7 @@ import { TableGroupInterpreter } from '@/core/interpreter/elementInterpreter/tab import { EnumInterpreter } from '@/core/interpreter/elementInterpreter/enum'; import { ProjectInterpreter } from '@/core/interpreter/elementInterpreter/project'; import { TablePartialInterpreter } from '@/core/interpreter/elementInterpreter/tablePartial'; +import { DiagramViewInterpreter } from '@/core/interpreter/elementInterpreter/diagramView'; import { RecordsInterpreter } from '@/core/interpreter/records'; import Report from '@/core/report'; import { getElementKind } from '@/core/analyzer/utils'; @@ -62,6 +63,7 @@ function convertEnvToDb (env: InterpreterDatabase): Database { project: Array.from(env.project.values())[0] || {}, tablePartials: Array.from(env.tablePartials.values()).map(processColumnInDb), records, + diagramViews: env.diagramViews ? Array.from(env.diagramViews.values()) : [], }; } @@ -88,6 +90,7 @@ export default class Interpreter { recordsElements: [], cachedMergedTables: new Map(), source: ast.source, + diagramViews: new Map(), }; } @@ -109,6 +112,8 @@ export default class Interpreter { return (new EnumInterpreter(element, this.env)).interpret(); case ElementKind.Project: return (new ProjectInterpreter(element, this.env)).interpret(); + case ElementKind.DiagramView: + return (new DiagramViewInterpreter(element, this.env)).interpret(); case ElementKind.Records: // Defer records interpretation - collect for later this.env.recordsElements.push(element); diff --git a/packages/dbml-parse/src/core/interpreter/types.ts b/packages/dbml-parse/src/core/interpreter/types.ts index 4221aa728..68a89b154 100644 --- a/packages/dbml-parse/src/core/interpreter/types.ts +++ b/packages/dbml-parse/src/core/interpreter/types.ts @@ -11,6 +11,26 @@ export interface ElementInterpreter { interpret(): CompileError[]; } +/** + * FilterConfig is a tri-state filter: + * - [] (empty array) = show all + * - [...] (array with items) = show only these specific items + * - null = hide all + */ +export interface FilterConfig { + tables: Array<{ name: string; schemaName: string }> | null; + stickyNotes: Array<{ name: string }> | null; + tableGroups: Array<{ name: string }> | null; + schemas: Array<{ name: string }> | null; +} + +export interface DiagramView { + name: string; + schemaName: string | null; + visibleEntities: FilterConfig; + token: TokenPosition; +} + export interface InterpreterDatabase { schema: []; tables: Map; @@ -28,6 +48,7 @@ export interface InterpreterDatabase { recordsElements: ElementDeclarationNode[]; cachedMergedTables: Map; // map Table to Table that has been merged with table partials source: string; + diagramViews?: Map; } // Record value type @@ -71,6 +92,7 @@ export interface Database { project: Project; tablePartials: TablePartial[]; records: TableRecord[]; + diagramViews: DiagramView[]; } export interface Table { From 6999efa407d02b9eefe9311969c4f86551953f75 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Thu, 12 Mar 2026 18:22:32 +0700 Subject: [PATCH 06/31] feat(dbml): add syncDiagramView transform function - Add syncDiagramView function for synchronizing DiagramView blocks - Support create, update, delete operations on DiagramView blocks - Generate properly formatted DiagramView DBML syntax - Export DiagramView, FilterConfig, and DiagramViewSyncOperation types - Export syncDiagramView from package index Co-Authored-By: Claude Opus 4.6 --- .../queries/transform/syncDiagramView.ts | 220 ++++++++++++++++++ packages/dbml-parse/src/index.ts | 6 + 2 files changed, 226 insertions(+) create mode 100644 packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts diff --git a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts new file mode 100644 index 000000000..8532a6a97 --- /dev/null +++ b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts @@ -0,0 +1,220 @@ +import { applyTextEdits, TextEdit } from './applyTextEdits'; +import Lexer from '@/core/lexer/lexer'; +import Parser from '@/core/parser/parser'; +import { SyntaxNodeIdGenerator } from '@/core/analyzer/symbol/symbols'; + +export interface DiagramViewSyncOperation { + operation: 'create' | 'update' | 'delete'; + name: string; + newName?: string; + visibleEntities?: { + tables?: Array<{ name: string; schemaName: string }> | null; + stickyNotes?: Array<{ name: string }> | null; + tableGroups?: Array<{ name: string }> | null; + schemas?: Array<{ name: string }> | null; + }; +} + +interface DiagramViewBlock { + name: string; + startIndex: number; + endIndex: number; +} + +function findDiagramViewBlocks (source: string): DiagramViewBlock[] { + const blocks: DiagramViewBlock[] = []; + const lexerResult = new Lexer(source).lex(); + if (!lexerResult.isOk()) return blocks; + + const tokens = lexerResult.unwrap(); + const ast = new Parser(source, tokens, new SyntaxNodeIdGenerator()).parse(); + if (!ast.isOk()) return blocks; + + const program = ast.unwrap().ast; + + for (const element of program.body) { + if (element.type?.value === 'DiagramView') { + const name = element.name?.toString() || ''; + blocks.push({ + name, + startIndex: element.start, + endIndex: element.end, + }); + } + } + + return blocks; +} + +function generateDiagramViewBlock ( + name: string, + visibleEntities: DiagramViewSyncOperation['visibleEntities'], +): string { + const lines: string[] = [`DiagramView ${name} {`]; + + // Tables + if (visibleEntities?.tables !== undefined) { + if (visibleEntities.tables === null) { + // Hide all - don't add block + } else if (visibleEntities.tables.length === 0) { + lines.push(' Tables { * }'); + } else { + const tableNames = visibleEntities.tables.map((t) => + t.schemaName === 'public' ? t.name : `${t.schemaName}.${t.name}`, + ); + lines.push(' Tables {'); + tableNames.forEach((n) => lines.push(` ${n}`)); + lines.push(' }'); + } + } + + // Notes + if (visibleEntities?.stickyNotes !== undefined) { + if (visibleEntities.stickyNotes === null) { + // Hide all - don't add block + } else if (visibleEntities.stickyNotes.length === 0) { + lines.push(' Notes { * }'); + } else { + lines.push(' Notes {'); + visibleEntities.stickyNotes.forEach((n) => lines.push(` ${n.name}`)); + lines.push(' }'); + } + } + + // TableGroups + if (visibleEntities?.tableGroups !== undefined) { + if (visibleEntities.tableGroups === null) { + // Hide all - don't add block + } else if (visibleEntities.tableGroups.length === 0) { + lines.push(' TableGroups { * }'); + } else { + lines.push(' TableGroups {'); + visibleEntities.tableGroups.forEach((n) => lines.push(` ${n.name}`)); + lines.push(' }'); + } + } + + // Schemas + if (visibleEntities?.schemas !== undefined) { + if (visibleEntities.schemas === null) { + // Hide all - don't add block + } else if (visibleEntities.schemas.length === 0) { + lines.push(' Schemas { * }'); + } else { + lines.push(' Schemas {'); + visibleEntities.schemas.forEach((n) => lines.push(` ${n.name}`)); + lines.push(' }'); + } + } + + lines.push('}'); + return lines.join('\n'); +} + +/** + * Synchronizes DiagramView blocks in DBML source code. + * + * @param dbml - The original DBML source code + * @param operations - Array of operations to apply (create, update, delete) + * @returns Object containing the new DBML source code + */ +export function syncDiagramView ( + dbml: string, + operations: DiagramViewSyncOperation[], +): { newDbml: string } { + let currentDbml = dbml; + + for (const op of operations) { + currentDbml = applyOperation(currentDbml, op); + } + + return { newDbml: currentDbml }; +} + +function applyOperation (dbml: string, operation: DiagramViewSyncOperation): string { + switch (operation.operation) { + case 'create': + return applyCreate(dbml, operation); + case 'update': { + const blocks = findDiagramViewBlocks(dbml); + return applyUpdate(dbml, operation, blocks); + } + case 'delete': { + const blocks = findDiagramViewBlocks(dbml); + return applyDelete(dbml, operation, blocks); + } + default: + return dbml; + } +} + +function applyCreate (dbml: string, operation: DiagramViewSyncOperation): string { + const newBlock = generateDiagramViewBlock(operation.name, operation.visibleEntities); + + // Append at end of file + return dbml.trimEnd() + '\n\n' + newBlock + '\n'; +} + +function applyUpdate ( + dbml: string, + operation: DiagramViewSyncOperation, + blocks: DiagramViewBlock[], +): string { + const block = blocks.find((b) => b.name === operation.name); + if (!block) return dbml; + + const edits: TextEdit[] = []; + + if (operation.newName || operation.visibleEntities) { + // Generate new block content + const newName = operation.newName || operation.name; + const newBlock = generateDiagramViewBlock(newName, operation.visibleEntities); + + // Replace entire block + edits.push({ + start: block.startIndex, + end: block.endIndex, + newText: newBlock, + }); + } + + return applyTextEdits(dbml, edits); +} + +function applyDelete ( + dbml: string, + operation: DiagramViewSyncOperation, + blocks: DiagramViewBlock[], +): string { + const block = blocks.find((b) => b.name === operation.name); + if (!block) return dbml; + + // Remove block and surrounding whitespace + const lines = dbml.split('\n'); + let startLine = 0; + let endLine = lines.length - 1; + + // Find line boundaries + let currentPos = 0; + for (let i = 0; i < lines.length; i++) { + const lineStart = currentPos; + const lineEnd = currentPos + lines[i].length; + + if (lineStart <= block.startIndex && block.startIndex <= lineEnd) { + startLine = i; + } + if (lineStart <= block.endIndex && block.endIndex <= lineEnd) { + endLine = i; + } + + currentPos = lineEnd + 1; // +1 for newline + } + + // Remove lines and clean up extra blank lines + const newLines = [ + ...lines.slice(0, startLine), + ...lines.slice(endLine + 1), + ]; + + return newLines.join('\n'); +} diff --git a/packages/dbml-parse/src/index.ts b/packages/dbml-parse/src/index.ts index 4601e4f88..c7e7b182d 100644 --- a/packages/dbml-parse/src/index.ts +++ b/packages/dbml-parse/src/index.ts @@ -56,6 +56,12 @@ export { type Project, type TableGroup, type TablePartial, + type DiagramView, + type FilterConfig, } from '@/core/interpreter/types'; +// Export syncDiagramView transform +export { syncDiagramView } from '@/compiler/queries/transform/syncDiagramView'; +export type { DiagramViewSyncOperation } from '@/compiler/queries/transform/syncDiagramView'; + export { Compiler, services }; From c2dc7355b820749975aec6af82c0454f9cc9a097 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Fri, 13 Mar 2026 02:26:11 +0700 Subject: [PATCH 07/31] Fix bugs --- packages/dbml-core/src/index.ts | 9 ++++++++ .../queries/transform/syncDiagramView.ts | 10 ++++----- .../binder/elementBinder/diagramView.ts | 21 ++++++++++++++----- .../dbml-parse/src/core/analyzer/utils.ts | 1 + .../elementValidators/diagramView.ts | 21 ++++++++++++++----- .../elementInterpreter/diagramView.ts | 17 ++++++++++++--- 6 files changed, 61 insertions(+), 18 deletions(-) diff --git a/packages/dbml-core/src/index.ts b/packages/dbml-core/src/index.ts index 46bdae035..02a84710d 100644 --- a/packages/dbml-core/src/index.ts +++ b/packages/dbml-core/src/index.ts @@ -36,4 +36,13 @@ export { tryExtractEnum, addDoubleQuoteIfNeeded, formatRecordValue, + // DiagramView exports + syncDiagramView, +} from '@dbml/parse'; + +// Re-export types +export type { + DiagramViewSyncOperation, + DiagramView, + FilterConfig, } from '@dbml/parse'; diff --git a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts index 8532a6a97..ee737e5b4 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts @@ -1,7 +1,7 @@ import { applyTextEdits, TextEdit } from './applyTextEdits'; import Lexer from '@/core/lexer/lexer'; import Parser from '@/core/parser/parser'; -import { SyntaxNodeIdGenerator } from '@/core/analyzer/symbol/symbols'; +import { SyntaxNodeIdGenerator } from '@/core/parser/nodes'; export interface DiagramViewSyncOperation { operation: 'create' | 'update' | 'delete'; @@ -24,13 +24,13 @@ interface DiagramViewBlock { function findDiagramViewBlocks (source: string): DiagramViewBlock[] { const blocks: DiagramViewBlock[] = []; const lexerResult = new Lexer(source).lex(); - if (!lexerResult.isOk()) return blocks; + if (lexerResult.getErrors().length > 0) return blocks; - const tokens = lexerResult.unwrap(); + const tokens = lexerResult.getValue(); const ast = new Parser(source, tokens, new SyntaxNodeIdGenerator()).parse(); - if (!ast.isOk()) return blocks; + if (ast.getErrors().length > 0) return blocks; - const program = ast.unwrap().ast; + const program = ast.getValue().ast; for (const element of program.body) { if (element.type?.value === 'DiagramView') { diff --git a/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts b/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts index b40cd73f8..605173cae 100644 --- a/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts +++ b/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts @@ -1,6 +1,6 @@ import { partition } from 'lodash-es'; import { - BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ProgramNode, + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, PrimaryExpressionNode, ProgramNode, VariableNode, } from '../../../parser/nodes'; import { ElementBinder } from '../types'; import { SyntaxToken } from '../../../lexer/tokens'; @@ -9,6 +9,17 @@ import { lookupAndBindInScope, pickBinder, scanNonListNodeForBinding } from '../ import { SymbolKind } from '../../symbol/symbolIndex'; import SymbolFactory from '../../symbol/factory'; +/** + * Check if a node is a wildcard expression (*) + */ +function isWildcardExpression (node: unknown): boolean { + if (!node) return false; + if (node instanceof PrimaryExpressionNode && node.expression instanceof VariableNode) { + return node.expression.variable?.value === '*'; + } + return false; +} + export default class DiagramViewBinder implements ElementBinder { private symbolFactory: SymbolFactory; private declarationNode: ElementDeclarationNode & { type: SyntaxToken }; @@ -81,7 +92,7 @@ export default class DiagramViewBinder implements ElementBinder { } // Skip wildcard - if (field.callee.type === 'WildcardExpression') { + if (isWildcardExpression(field.callee)) { return []; } @@ -112,7 +123,7 @@ export default class DiagramViewBinder implements ElementBinder { } // Skip wildcard - if (field.callee.type === 'WildcardExpression') { + if (isWildcardExpression(field.callee)) { return []; } @@ -140,7 +151,7 @@ export default class DiagramViewBinder implements ElementBinder { } // Skip wildcard - if (field.callee.type === 'WildcardExpression') { + if (isWildcardExpression(field.callee)) { return []; } @@ -170,7 +181,7 @@ export default class DiagramViewBinder implements ElementBinder { } // Skip wildcard - if (field.callee.type === 'WildcardExpression') { + if (isWildcardExpression(field.callee)) { return []; } diff --git a/packages/dbml-parse/src/core/analyzer/utils.ts b/packages/dbml-parse/src/core/analyzer/utils.ts index 11a4762e4..6e5f92b4a 100644 --- a/packages/dbml-parse/src/core/analyzer/utils.ts +++ b/packages/dbml-parse/src/core/analyzer/utils.ts @@ -36,6 +36,7 @@ export function getElementKind (node?: ElementDeclarationNode): Option e instanceof FunctionApplicationNode && e.callee?.type === 'WildcardExpression', + (e) => e instanceof FunctionApplicationNode && isWildcardExpression(e.callee), ); const hasSpecificItems = body.body.some( - (e) => e instanceof FunctionApplicationNode && e.callee?.type !== 'WildcardExpression', + (e) => e instanceof FunctionApplicationNode && !isWildcardExpression(e.callee), ); if (hasWildcard && hasSpecificItems) { diff --git a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts index b240fdb32..f5303232f 100644 --- a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts +++ b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts @@ -1,11 +1,22 @@ import { partition } from 'lodash-es'; import { destructureComplexVariable } from '@/core/analyzer/utils'; import { CompileError } from '@/core/errors'; -import { BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, SyntaxNode } from '@/core/parser/nodes'; +import { BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, PrimaryExpressionNode, SyntaxNode, VariableNode } from '@/core/parser/nodes'; import { ElementInterpreter, InterpreterDatabase, DiagramView, FilterConfig } from '@/core/interpreter/types'; import { getTokenPosition } from '@/core/interpreter/utils'; import { DEFAULT_SCHEMA_NAME } from '@/constants'; +/** + * Check if a node is a wildcard expression (*) + */ +function isWildcardExpression (node: SyntaxNode | undefined): boolean { + if (!node) return false; + if (node instanceof PrimaryExpressionNode && node.expression instanceof VariableNode) { + return node.expression.variable?.value === '*'; + } + return false; +} + export class DiagramViewInterpreter implements ElementInterpreter { private declarationNode: ElementDeclarationNode; private env: InterpreterDatabase; @@ -62,7 +73,7 @@ export class DiagramViewInterpreter implements ElementInterpreter { // Check for shorthand { * } if (body.body.length === 1) { const first = body.body[0]; - if (first instanceof FunctionApplicationNode && first.callee?.type === 'WildcardExpression') { + if (first instanceof FunctionApplicationNode && isWildcardExpression(first.callee)) { // Show all entities this.diagramView.visibleEntities = { tables: [], @@ -91,7 +102,7 @@ export class DiagramViewInterpreter implements ElementInterpreter { // Check for wildcard const hasWildcard = body.body.some( - (e) => e instanceof FunctionApplicationNode && e.callee?.type === 'WildcardExpression', + (e) => e instanceof FunctionApplicationNode && isWildcardExpression(e.callee), ); if (hasWildcard) { From 1d67e74113e8f8689436d18f6c1c29e56d92c410 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Fri, 13 Mar 2026 17:24:48 +0700 Subject: [PATCH 08/31] Draft 1 --- .../examples/compiler/syncDiagramView.test.ts | 129 ++ .../parser/output/call_expression.out.json | 1234 +++++++++-------- .../parser/output/expression.out.json | 987 +++++++------ .../queries/transform/syncDiagramView.ts | 26 +- .../elementValidators/diagramView.ts | 3 + packages/dbml-parse/src/core/parser/parser.ts | 9 +- 6 files changed, 1310 insertions(+), 1078 deletions(-) create mode 100644 packages/dbml-parse/__tests__/examples/compiler/syncDiagramView.test.ts diff --git a/packages/dbml-parse/__tests__/examples/compiler/syncDiagramView.test.ts b/packages/dbml-parse/__tests__/examples/compiler/syncDiagramView.test.ts new file mode 100644 index 000000000..eae3cbc11 --- /dev/null +++ b/packages/dbml-parse/__tests__/examples/compiler/syncDiagramView.test.ts @@ -0,0 +1,129 @@ +import { describe, expect, it } from 'vitest'; +import { syncDiagramView } from '@/compiler/queries/transform/syncDiagramView'; +import Compiler from '@/compiler/index'; +import Lexer from '@/core/lexer/lexer'; +import Parser from '@/core/parser/parser'; +import { SyntaxNodeIdGenerator } from '@/core/parser/nodes'; + +// ─── update ──────────────────────────────────────────────────────────────── + +describe('syncDiagramView - update', () => { + it('renames an existing DiagramView block', () => { + const dbml = `DiagramView my_view {\n Tables { users }\n}`; + const { newDbml } = syncDiagramView(dbml, [ + { operation: 'update', name: 'my_view', newName: 'renamed_view' }, + ]); + expect(newDbml).toContain('DiagramView renamed_view'); + expect(newDbml).not.toContain('DiagramView my_view'); + }); +}); + +// ─── delete ──────────────────────────────────────────────────────────────── + +describe('syncDiagramView - delete', () => { + it('removes an existing DiagramView block', () => { + const dbml = `Table users {\n id int\n}\n\nDiagramView my_view {\n Tables { users }\n}`; + const { newDbml } = syncDiagramView(dbml, [ + { operation: 'delete', name: 'my_view' }, + ]); + expect(newDbml).not.toContain('DiagramView my_view'); + expect(newDbml).toContain('Table users'); + }); +}); + +// ─── * wildcard in parser ────────────────────────────────────────────────── + +describe('Parser - * wildcard in DiagramView', () => { + it('parses DiagramView with { * } without errors', () => { + const source = `DiagramView v { * }`; + const tokens = new Lexer(source).lex().getValue(); + const result = new Parser(source, tokens, new SyntaxNodeIdGenerator()).parse(); + expect(result.getErrors()).toHaveLength(0); + }); + + it('parses DiagramView Tables { * } without errors', () => { + const source = `DiagramView v {\n Tables { * }\n}`; + const tokens = new Lexer(source).lex().getValue(); + const result = new Parser(source, tokens, new SyntaxNodeIdGenerator()).parse(); + expect(result.getErrors()).toHaveLength(0); + }); + + it('validates DiagramView with multiple sub-blocks each using * without errors', () => { + const source = `DiagramView "New View" {\n Tables { * }\n Notes { * }\n TableGroups { * }\n Schemas { * }\n}`; + const compiler = new Compiler(); + compiler.setSource(source); + expect(compiler.parse.errors()).toHaveLength(0); + }); +}); + +// ─── name quoting ────────────────────────────────────────────────────────── + +describe('syncDiagramView - name quoting', () => { + it('quotes names containing spaces', () => { + const { newDbml } = syncDiagramView('', [ + { + operation: 'create', + name: 'My View', + visibleEntities: { tables: null, stickyNotes: null, tableGroups: null, schemas: null }, + }, + ]); + expect(newDbml).toContain('DiagramView "My View"'); + }); + + it('does not quote simple identifier names', () => { + const { newDbml } = syncDiagramView('', [ + { + operation: 'create', + name: 'my_view', + visibleEntities: { tables: null, stickyNotes: null, tableGroups: null, schemas: null }, + }, + ]); + expect(newDbml).toContain('DiagramView my_view'); + expect(newDbml).not.toContain('"my_view"'); + }); + + it('escapes internal double quotes in names', () => { + const { newDbml } = syncDiagramView('', [ + { + operation: 'create', + name: 'My "Special" View', + visibleEntities: { tables: null, stickyNotes: null, tableGroups: null, schemas: null }, + }, + ]); + expect(newDbml).toContain('DiagramView "My \\"Special\\" View"'); + }); + + it('can find and update a block with a quoted name', () => { + const dbml = `DiagramView "My View" {\n Tables { users }\n}`; + const { newDbml } = syncDiagramView(dbml, [ + { operation: 'update', name: 'My View', newName: 'New Name' }, + ]); + expect(newDbml).toContain('DiagramView "New Name"'); + expect(newDbml).not.toContain('"My View"'); + }); +}); + +// ─── idempotent create ──────────────────────────────────────────────────── + +describe('syncDiagramView - idempotent create', () => { + it('treats create as update when block with same name already exists', () => { + const dbml = `DiagramView my_view {\n Tables { users }\n}`; + const { newDbml } = syncDiagramView(dbml, [ + { + operation: 'create', + name: 'my_view', + visibleEntities: { + tables: [{ name: 'posts', schemaName: 'public' }], + stickyNotes: null, + tableGroups: null, + schemas: null, + }, + }, + ]); + // Should not create a second DiagramView block + const matches = [...newDbml.matchAll(/DiagramView my_view/g)]; + expect(matches).toHaveLength(1); + // Should have updated content with posts + expect(newDbml).toContain('posts'); + }); +}); diff --git a/packages/dbml-parse/__tests__/snapshots/parser/output/call_expression.out.json b/packages/dbml-parse/__tests__/snapshots/parser/output/call_expression.out.json index e8430ee44..bff731842 100644 --- a/packages/dbml-parse/__tests__/snapshots/parser/output/call_expression.out.json +++ b/packages/dbml-parse/__tests__/snapshots/parser/output/call_expression.out.json @@ -1,6 +1,6 @@ { "value": { - "id": 23, + "id": 32, "kind": "", "startPos": { "offset": 0, @@ -18,7 +18,7 @@ "end": 69, "body": [ { - "id": 22, + "id": 31, "kind": "", "startPos": { "offset": 0, @@ -157,7 +157,7 @@ } }, "body": { - "id": 21, + "id": 30, "kind": "", "startPos": { "offset": 20, @@ -218,7 +218,7 @@ }, "body": [ { - "id": 6, + "id": 21, "kind": "", "startPos": { "offset": 27, @@ -227,15 +227,15 @@ }, "fullStart": 23, "endPos": { - "offset": 31, - "line": 1, + "offset": 53, + "line": 2, "column": 8 }, - "fullEnd": 31, + "fullEnd": 55, "start": 27, - "end": 31, + "end": 53, "callee": { - "id": 5, + "id": 6, "kind": "", "startPos": { "offset": 27, @@ -248,7 +248,7 @@ "line": 1, "column": 8 }, - "fullEnd": 31, + "fullEnd": 32, "start": 27, "end": 31, "op": { @@ -267,308 +267,7 @@ "leadingTrivia": [], "trailingTrivia": [], "leadingInvalid": [], - "trailingInvalid": [ - { - "kind": "", - "startPos": { - "offset": 30, - "line": 1, - "column": 7 - }, - "endPos": { - "offset": 31, - "line": 1, - "column": 8 - }, - "value": "*", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 31, - "line": 1, - "column": 8 - }, - "endPos": { - "offset": 32, - "line": 1, - "column": 9 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 31, - "end": 32 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 30, - "end": 31 - }, - { - "kind": "", - "startPos": { - "offset": 32, - "line": 1, - "column": 9 - }, - "endPos": { - "offset": 33, - "line": 1, - "column": 10 - }, - "value": "2", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 33, - "line": 1, - "column": 10 - }, - "endPos": { - "offset": 34, - "line": 1, - "column": 11 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 33, - "end": 34 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 32, - "end": 33 - }, - { - "kind": "", - "startPos": { - "offset": 34, - "line": 1, - "column": 11 - }, - "endPos": { - "offset": 35, - "line": 1, - "column": 12 - }, - "value": "+", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 35, - "line": 1, - "column": 12 - }, - "endPos": { - "offset": 36, - "line": 1, - "column": 13 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 35, - "end": 36 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 34, - "end": 35 - }, - { - "kind": "", - "startPos": { - "offset": 36, - "line": 1, - "column": 13 - }, - "endPos": { - "offset": 37, - "line": 1, - "column": 14 - }, - "value": "3", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 37, - "line": 1, - "column": 14 - }, - "endPos": { - "offset": 38, - "line": 1, - "column": 15 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 37, - "end": 38 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 36, - "end": 37 - }, - { - "kind": "", - "startPos": { - "offset": 38, - "line": 1, - "column": 15 - }, - "endPos": { - "offset": 39, - "line": 1, - "column": 16 - }, - "value": "(", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 38, - "end": 39 - }, - { - "kind": "", - "startPos": { - "offset": 39, - "line": 1, - "column": 16 - }, - "endPos": { - "offset": 40, - "line": 1, - "column": 17 - }, - "value": ")", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 40, - "line": 1, - "column": 17 - }, - "endPos": { - "offset": 41, - "line": 1, - "column": 18 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 40, - "end": 41 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 39, - "end": 40 - }, - { - "kind": "", - "startPos": { - "offset": 41, - "line": 1, - "column": 18 - }, - "endPos": { - "offset": 42, - "line": 1, - "column": 19 - }, - "value": "(", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 41, - "end": 42 - }, - { - "kind": "", - "startPos": { - "offset": 42, - "line": 1, - "column": 19 - }, - "endPos": { - "offset": 43, - "line": 1, - "column": 20 - }, - "value": ")", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 44, - "line": 1, - "column": 21 - }, - "endPos": { - "offset": 45, - "line": 2, - "column": 0 - }, - "value": "\n", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 44, - "end": 45 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 42, - "end": 43 - } - ], + "trailingInvalid": [], "isInvalid": false, "start": 29, "end": 30 @@ -738,77 +437,103 @@ } }, "rightExpression": { - "id": 4, - "kind": "", + "id": 5, + "kind": "", "startPos": { - "offset": 31, + "offset": 30, "line": 1, - "column": 8 + "column": 7 }, - "fullStart": 31, + "fullStart": 30, "endPos": { "offset": 31, "line": 1, "column": 8 }, - "fullEnd": 31, - "start": 31, - "end": 31 + "fullEnd": 32, + "start": 30, + "end": 31, + "expression": { + "id": 4, + "kind": "", + "startPos": { + "offset": 30, + "line": 1, + "column": 7 + }, + "fullStart": 30, + "endPos": { + "offset": 31, + "line": 1, + "column": 8 + }, + "fullEnd": 32, + "start": 30, + "end": 31, + "variable": { + "kind": "", + "startPos": { + "offset": 30, + "line": 1, + "column": 7 + }, + "endPos": { + "offset": 31, + "line": 1, + "column": 8 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 31, + "line": 1, + "column": 8 + }, + "endPos": { + "offset": 32, + "line": 1, + "column": 9 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 31, + "end": 32 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 30, + "end": 31 + } + } } }, - "args": [] - }, - { - "id": 12, - "kind": "", - "startPos": { - "offset": 49, - "line": 2, - "column": 4 - }, - "fullStart": 45, - "endPos": { - "offset": 53, - "line": 2, - "column": 8 - }, - "fullEnd": 55, - "start": 49, - "end": 53, - "callee": { - "id": 11, - "kind": "", - "startPos": { - "offset": 49, - "line": 2, - "column": 4 - }, - "fullStart": 45, - "endPos": { - "offset": 53, - "line": 2, - "column": 8 - }, - "fullEnd": 55, - "start": 49, - "end": 53, - "callee": { - "id": 9, - "kind": "", + "args": [ + { + "id": 20, + "kind": "", "startPos": { - "offset": 49, - "line": 2, - "column": 4 + "offset": 32, + "line": 1, + "column": 9 }, - "fullStart": 45, + "fullStart": 32, "endPos": { - "offset": 51, + "offset": 53, "line": 2, - "column": 6 + "column": 8 }, - "fullEnd": 51, - "start": 49, - "end": 51, + "fullEnd": 55, + "start": 32, + "end": 53, "op": { "kind": "", "startPos": { @@ -865,59 +590,482 @@ "start": 46, "end": 47 }, - { - "kind": "", + { + "kind": "", + "startPos": { + "offset": 47, + "line": 2, + "column": 2 + }, + "endPos": { + "offset": 48, + "line": 2, + "column": 3 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 47, + "end": 48 + }, + { + "kind": "", + "startPos": { + "offset": 48, + "line": 2, + "column": 3 + }, + "endPos": { + "offset": 49, + "line": 2, + "column": 4 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 48, + "end": 49 + } + ], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 49, + "end": 50 + }, + "leftExpression": { + "id": 15, + "kind": "", + "startPos": { + "offset": 32, + "line": 1, + "column": 9 + }, + "fullStart": 32, + "endPos": { + "offset": 43, + "line": 1, + "column": 20 + }, + "fullEnd": 45, + "start": 32, + "end": 43, + "op": { + "kind": "", + "startPos": { + "offset": 34, + "line": 1, + "column": 11 + }, + "endPos": { + "offset": 35, + "line": 1, + "column": 12 + }, + "value": "+", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 35, + "line": 1, + "column": 12 + }, + "endPos": { + "offset": 36, + "line": 1, + "column": 13 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 35, + "end": 36 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 34, + "end": 35 + }, + "leftExpression": { + "id": 8, + "kind": "", + "startPos": { + "offset": 32, + "line": 1, + "column": 9 + }, + "fullStart": 32, + "endPos": { + "offset": 33, + "line": 1, + "column": 10 + }, + "fullEnd": 34, + "start": 32, + "end": 33, + "expression": { + "id": 7, + "kind": "", + "startPos": { + "offset": 32, + "line": 1, + "column": 9 + }, + "fullStart": 32, + "endPos": { + "offset": 33, + "line": 1, + "column": 10 + }, + "fullEnd": 34, + "start": 32, + "end": 33, + "literal": { + "kind": "", + "startPos": { + "offset": 32, + "line": 1, + "column": 9 + }, + "endPos": { + "offset": 33, + "line": 1, + "column": 10 + }, + "value": "2", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 33, + "line": 1, + "column": 10 + }, + "endPos": { + "offset": 34, + "line": 1, + "column": 11 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 33, + "end": 34 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 32, + "end": 33 + } + } + }, + "rightExpression": { + "id": 14, + "kind": "", + "startPos": { + "offset": 36, + "line": 1, + "column": 13 + }, + "fullStart": 36, + "endPos": { + "offset": 43, + "line": 1, + "column": 20 + }, + "fullEnd": 45, + "start": 36, + "end": 43, + "callee": { + "id": 12, + "kind": "", "startPos": { - "offset": 47, - "line": 2, - "column": 2 + "offset": 36, + "line": 1, + "column": 13 }, + "fullStart": 36, "endPos": { - "offset": 48, - "line": 2, - "column": 3 + "offset": 40, + "line": 1, + "column": 17 }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 47, - "end": 48 + "fullEnd": 41, + "start": 36, + "end": 40, + "callee": { + "id": 10, + "kind": "", + "startPos": { + "offset": 36, + "line": 1, + "column": 13 + }, + "fullStart": 36, + "endPos": { + "offset": 37, + "line": 1, + "column": 14 + }, + "fullEnd": 38, + "start": 36, + "end": 37, + "expression": { + "id": 9, + "kind": "", + "startPos": { + "offset": 36, + "line": 1, + "column": 13 + }, + "fullStart": 36, + "endPos": { + "offset": 37, + "line": 1, + "column": 14 + }, + "fullEnd": 38, + "start": 36, + "end": 37, + "literal": { + "kind": "", + "startPos": { + "offset": 36, + "line": 1, + "column": 13 + }, + "endPos": { + "offset": 37, + "line": 1, + "column": 14 + }, + "value": "3", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 37, + "line": 1, + "column": 14 + }, + "endPos": { + "offset": 38, + "line": 1, + "column": 15 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 37, + "end": 38 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 36, + "end": 37 + } + } + }, + "argumentList": { + "id": 11, + "kind": "", + "startPos": { + "offset": 38, + "line": 1, + "column": 15 + }, + "fullStart": 38, + "endPos": { + "offset": 40, + "line": 1, + "column": 17 + }, + "fullEnd": 41, + "start": 38, + "end": 40, + "tupleOpenParen": { + "kind": "", + "startPos": { + "offset": 38, + "line": 1, + "column": 15 + }, + "endPos": { + "offset": 39, + "line": 1, + "column": 16 + }, + "value": "(", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 38, + "end": 39 + }, + "elementList": [], + "commaList": [], + "tupleCloseParen": { + "kind": "", + "startPos": { + "offset": 39, + "line": 1, + "column": 16 + }, + "endPos": { + "offset": 40, + "line": 1, + "column": 17 + }, + "value": ")", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 40, + "line": 1, + "column": 17 + }, + "endPos": { + "offset": 41, + "line": 1, + "column": 18 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 40, + "end": 41 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 39, + "end": 40 + } + } }, - { - "kind": "", + "argumentList": { + "id": 13, + "kind": "", "startPos": { - "offset": 48, - "line": 2, - "column": 3 + "offset": 41, + "line": 1, + "column": 18 }, + "fullStart": 41, "endPos": { - "offset": 49, - "line": 2, - "column": 4 + "offset": 43, + "line": 1, + "column": 20 }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 48, - "end": 49 + "fullEnd": 45, + "start": 41, + "end": 43, + "tupleOpenParen": { + "kind": "", + "startPos": { + "offset": 41, + "line": 1, + "column": 18 + }, + "endPos": { + "offset": 42, + "line": 1, + "column": 19 + }, + "value": "(", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 41, + "end": 42 + }, + "elementList": [], + "commaList": [], + "tupleCloseParen": { + "kind": "", + "startPos": { + "offset": 42, + "line": 1, + "column": 19 + }, + "endPos": { + "offset": 43, + "line": 1, + "column": 20 + }, + "value": ")", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 44, + "line": 1, + "column": 21 + }, + "endPos": { + "offset": 45, + "line": 2, + "column": 0 + }, + "value": "\n", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 44, + "end": 45 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 42, + "end": 43 + } } - ], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 49, - "end": 50 + } }, - "expression": { - "id": 8, - "kind": "", + "rightExpression": { + "id": 19, + "kind": "", "startPos": { "offset": 50, "line": 2, @@ -925,16 +1073,16 @@ }, "fullStart": 50, "endPos": { - "offset": 51, + "offset": 53, "line": 2, - "column": 6 + "column": 8 }, - "fullEnd": 51, + "fullEnd": 55, "start": 50, - "end": 51, - "expression": { - "id": 7, - "kind": "", + "end": 53, + "callee": { + "id": 17, + "kind": "", "startPos": { "offset": 50, "line": 2, @@ -949,119 +1097,136 @@ "fullEnd": 51, "start": 50, "end": 51, - "literal": { - "kind": "", + "expression": { + "id": 16, + "kind": "", "startPos": { "offset": 50, "line": 2, "column": 5 }, + "fullStart": 50, "endPos": { "offset": 51, "line": 2, "column": 6 }, - "value": "2", + "fullEnd": 51, + "start": 50, + "end": 51, + "literal": { + "kind": "", + "startPos": { + "offset": 50, + "line": 2, + "column": 5 + }, + "endPos": { + "offset": 51, + "line": 2, + "column": 6 + }, + "value": "2", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 50, + "end": 51 + } + } + }, + "argumentList": { + "id": 18, + "kind": "", + "startPos": { + "offset": 51, + "line": 2, + "column": 6 + }, + "fullStart": 51, + "endPos": { + "offset": 53, + "line": 2, + "column": 8 + }, + "fullEnd": 55, + "start": 51, + "end": 53, + "tupleOpenParen": { + "kind": "", + "startPos": { + "offset": 51, + "line": 2, + "column": 6 + }, + "endPos": { + "offset": 52, + "line": 2, + "column": 7 + }, + "value": "(", "leadingTrivia": [], "trailingTrivia": [], "leadingInvalid": [], "trailingInvalid": [], "isInvalid": false, - "start": 50, - "end": 51 - } - } - } - }, - "argumentList": { - "id": 10, - "kind": "", - "startPos": { - "offset": 51, - "line": 2, - "column": 6 - }, - "fullStart": 51, - "endPos": { - "offset": 53, - "line": 2, - "column": 8 - }, - "fullEnd": 55, - "start": 51, - "end": 53, - "tupleOpenParen": { - "kind": "", - "startPos": { - "offset": 51, - "line": 2, - "column": 6 - }, - "endPos": { - "offset": 52, - "line": 2, - "column": 7 - }, - "value": "(", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 51, - "end": 52 - }, - "elementList": [], - "commaList": [], - "tupleCloseParen": { - "kind": "", - "startPos": { - "offset": 52, - "line": 2, - "column": 7 - }, - "endPos": { - "offset": 53, - "line": 2, - "column": 8 - }, - "value": ")", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", + "start": 51, + "end": 52 + }, + "elementList": [], + "commaList": [], + "tupleCloseParen": { + "kind": "", "startPos": { - "offset": 54, + "offset": 52, "line": 2, - "column": 9 + "column": 7 }, "endPos": { - "offset": 55, - "line": 3, - "column": 0 + "offset": 53, + "line": 2, + "column": 8 }, - "value": "\n", + "value": ")", "leadingTrivia": [], - "trailingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 54, + "line": 2, + "column": 9 + }, + "endPos": { + "offset": 55, + "line": 3, + "column": 0 + }, + "value": "\n", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 54, + "end": 55 + } + ], "leadingInvalid": [], "trailingInvalid": [], "isInvalid": false, - "start": 54, - "end": 55 + "start": 52, + "end": 53 } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 52, - "end": 53 + } } } - }, - "args": [] + ] }, { - "id": 20, + "id": 29, "kind": "", "startPos": { "offset": 59, @@ -1078,7 +1243,7 @@ "start": 59, "end": 64, "callee": { - "id": 19, + "id": 28, "kind": "", "startPos": { "offset": 59, @@ -1095,7 +1260,7 @@ "start": 59, "end": 64, "callee": { - "id": 17, + "id": 26, "kind": "", "startPos": { "offset": 59, @@ -1133,7 +1298,7 @@ "end": 61 }, "leftExpression": { - "id": 14, + "id": 23, "kind": "", "startPos": { "offset": 59, @@ -1150,7 +1315,7 @@ "start": 59, "end": 60, "expression": { - "id": 13, + "id": 22, "kind": "", "startPos": { "offset": 59, @@ -1275,7 +1440,7 @@ } }, "rightExpression": { - "id": 16, + "id": 25, "kind": "", "startPos": { "offset": 61, @@ -1292,7 +1457,7 @@ "start": 61, "end": 62, "expression": { - "id": 15, + "id": 24, "kind": "", "startPos": { "offset": 61, @@ -1333,7 +1498,7 @@ } }, "argumentList": { - "id": 18, + "id": 27, "kind": "", "startPos": { "offset": 62, @@ -1488,56 +1653,5 @@ "end": 69 } }, - "errors": [ - { - "code": 1008, - "diagnostic": "Unexpected '*' in an expression", - "nodeOrToken": { - "kind": "", - "startPos": { - "offset": 30, - "line": 1, - "column": 7 - }, - "endPos": { - "offset": 31, - "line": 1, - "column": 8 - }, - "value": "*", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 31, - "line": 1, - "column": 8 - }, - "endPos": { - "offset": 32, - "line": 1, - "column": 9 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 31, - "end": 32 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 30, - "end": 31 - }, - "start": 30, - "end": 31, - "name": "CompileError" - } - ] + "errors": [] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/parser/output/expression.out.json b/packages/dbml-parse/__tests__/snapshots/parser/output/expression.out.json index c7fa035ec..4ab15e46c 100644 --- a/packages/dbml-parse/__tests__/snapshots/parser/output/expression.out.json +++ b/packages/dbml-parse/__tests__/snapshots/parser/output/expression.out.json @@ -1,6 +1,6 @@ { "value": { - "id": 217, + "id": 221, "kind": "", "startPos": { "offset": 0, @@ -18,7 +18,7 @@ "end": 463, "body": [ { - "id": 216, + "id": 220, "kind": "", "startPos": { "offset": 0, @@ -157,7 +157,7 @@ } }, "body": { - "id": 215, + "id": 219, "kind": "", "startPos": { "offset": 16, @@ -211,222 +211,293 @@ } ], "leadingInvalid": [], - "trailingInvalid": [ - { - "kind": "", + "trailingInvalid": [], + "isInvalid": false, + "start": 16, + "end": 17 + }, + "body": [ + { + "id": 7, + "kind": "", + "startPos": { + "offset": 23, + "line": 1, + "column": 4 + }, + "fullStart": 19, + "endPos": { + "offset": 26, + "line": 1, + "column": 7 + }, + "fullEnd": 28, + "start": 23, + "end": 26, + "callee": { + "id": 6, + "kind": "", "startPos": { "offset": 23, "line": 1, "column": 4 }, + "fullStart": 19, "endPos": { - "offset": 24, + "offset": 26, "line": 1, - "column": 5 + "column": 7 }, - "value": "*", - "leadingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 19, - "line": 1, - "column": 0 - }, - "endPos": { - "offset": 20, - "line": 1, - "column": 1 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 19, - "end": 20 + "fullEnd": 28, + "start": 23, + "end": 26, + "op": { + "kind": "", + "startPos": { + "offset": 24, + "line": 1, + "column": 5 }, - { - "kind": "", - "startPos": { - "offset": 20, - "line": 1, - "column": 1 - }, - "endPos": { - "offset": 21, - "line": 1, - "column": 2 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 20, - "end": 21 + "endPos": { + "offset": 25, + "line": 1, + "column": 6 }, - { - "kind": "", - "startPos": { - "offset": 21, - "line": 1, - "column": 2 - }, - "endPos": { - "offset": 22, - "line": 1, - "column": 3 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 21, - "end": 22 + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 24, + "end": 25 + }, + "leftExpression": { + "id": 3, + "kind": "", + "startPos": { + "offset": 23, + "line": 1, + "column": 4 }, - { - "kind": "", + "fullStart": 19, + "endPos": { + "offset": 24, + "line": 1, + "column": 5 + }, + "fullEnd": 24, + "start": 23, + "end": 24, + "expression": { + "id": 2, + "kind": "", "startPos": { - "offset": 22, + "offset": 23, "line": 1, - "column": 3 + "column": 4 }, + "fullStart": 19, "endPos": { - "offset": 23, + "offset": 24, "line": 1, - "column": 4 + "column": 5 }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 22, - "end": 23 + "fullEnd": 24, + "start": 23, + "end": 24, + "variable": { + "kind": "", + "startPos": { + "offset": 23, + "line": 1, + "column": 4 + }, + "endPos": { + "offset": 24, + "line": 1, + "column": 5 + }, + "value": "*", + "leadingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 19, + "line": 1, + "column": 0 + }, + "endPos": { + "offset": 20, + "line": 1, + "column": 1 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 19, + "end": 20 + }, + { + "kind": "", + "startPos": { + "offset": 20, + "line": 1, + "column": 1 + }, + "endPos": { + "offset": 21, + "line": 1, + "column": 2 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 20, + "end": 21 + }, + { + "kind": "", + "startPos": { + "offset": 21, + "line": 1, + "column": 2 + }, + "endPos": { + "offset": 22, + "line": 1, + "column": 3 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 21, + "end": 22 + }, + { + "kind": "", + "startPos": { + "offset": 22, + "line": 1, + "column": 3 + }, + "endPos": { + "offset": 23, + "line": 1, + "column": 4 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 22, + "end": 23 + } + ], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 23, + "end": 24 + } } - ], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 23, - "end": 24 - }, - { - "kind": "", - "startPos": { - "offset": 24, - "line": 1, - "column": 5 - }, - "endPos": { - "offset": 25, - "line": 1, - "column": 6 - }, - "value": "*", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 24, - "end": 25 - }, - { - "kind": "", - "startPos": { - "offset": 25, - "line": 1, - "column": 6 - }, - "endPos": { - "offset": 26, - "line": 1, - "column": 7 }, - "value": "b", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", + "rightExpression": { + "id": 5, + "kind": "", + "startPos": { + "offset": 25, + "line": 1, + "column": 6 + }, + "fullStart": 25, + "endPos": { + "offset": 26, + "line": 1, + "column": 7 + }, + "fullEnd": 28, + "start": 25, + "end": 26, + "expression": { + "id": 4, + "kind": "", "startPos": { - "offset": 27, + "offset": 25, "line": 1, - "column": 8 + "column": 6 }, + "fullStart": 25, "endPos": { - "offset": 28, - "line": 2, - "column": 0 + "offset": 26, + "line": 1, + "column": 7 }, - "value": "\n", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 27, - "end": 28 + "fullEnd": 28, + "start": 25, + "end": 26, + "variable": { + "kind": "", + "startPos": { + "offset": 25, + "line": 1, + "column": 6 + }, + "endPos": { + "offset": 26, + "line": 1, + "column": 7 + }, + "value": "b", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 27, + "line": 1, + "column": 8 + }, + "endPos": { + "offset": 28, + "line": 2, + "column": 0 + }, + "value": "\n", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 27, + "end": 28 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 25, + "end": 26 + } } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 25, - "end": 26 - } - ], - "isInvalid": false, - "start": 16, - "end": 17 - }, - "body": [ - { - "id": 3, - "kind": "", - "startPos": { - "offset": 24, - "line": 1, - "column": 5 - }, - "fullStart": 24, - "endPos": { - "offset": 24, - "line": 1, - "column": 5 - }, - "fullEnd": 24, - "start": 24, - "end": 24, - "callee": { - "id": 2, - "kind": "", - "startPos": { - "offset": 24, - "line": 1, - "column": 5 - }, - "fullStart": 24, - "endPos": { - "offset": 24, - "line": 1, - "column": 5 - }, - "fullEnd": 24, - "start": 24, - "end": 24 + } }, "args": [] }, { - "id": 12, + "id": 16, "kind": "", "startPos": { "offset": 38, @@ -443,7 +514,7 @@ "start": 38, "end": 47, "callee": { - "id": 11, + "id": 15, "kind": "", "startPos": { "offset": 38, @@ -503,7 +574,7 @@ "end": 41 }, "leftExpression": { - "id": 5, + "id": 9, "kind": "", "startPos": { "offset": 38, @@ -520,7 +591,7 @@ "start": 38, "end": 39, "expression": { - "id": 4, + "id": 8, "kind": "", "startPos": { "offset": 38, @@ -772,7 +843,7 @@ } }, "rightExpression": { - "id": 10, + "id": 14, "kind": "", "startPos": { "offset": 42, @@ -832,7 +903,7 @@ "end": 45 }, "leftExpression": { - "id": 7, + "id": 11, "kind": "", "startPos": { "offset": 42, @@ -849,7 +920,7 @@ "start": 42, "end": 43, "expression": { - "id": 6, + "id": 10, "kind": "", "startPos": { "offset": 42, @@ -911,7 +982,7 @@ } }, "rightExpression": { - "id": 9, + "id": 13, "kind": "", "startPos": { "offset": 46, @@ -928,7 +999,7 @@ "start": 46, "end": 47, "expression": { - "id": 8, + "id": 12, "kind": "", "startPos": { "offset": 46, @@ -994,7 +1065,7 @@ "args": [] }, { - "id": 21, + "id": 25, "kind": "", "startPos": { "offset": 53, @@ -1011,7 +1082,7 @@ "start": 53, "end": 62, "callee": { - "id": 20, + "id": 24, "kind": "", "startPos": { "offset": 53, @@ -1071,7 +1142,7 @@ "end": 60 }, "leftExpression": { - "id": 17, + "id": 21, "kind": "", "startPos": { "offset": 53, @@ -1131,7 +1202,7 @@ "end": 56 }, "leftExpression": { - "id": 14, + "id": 18, "kind": "", "startPos": { "offset": 53, @@ -1148,7 +1219,7 @@ "start": 53, "end": 54, "expression": { - "id": 13, + "id": 17, "kind": "", "startPos": { "offset": 53, @@ -1295,7 +1366,7 @@ } }, "rightExpression": { - "id": 16, + "id": 20, "kind": "", "startPos": { "offset": 57, @@ -1312,7 +1383,7 @@ "start": 57, "end": 58, "expression": { - "id": 15, + "id": 19, "kind": "", "startPos": { "offset": 57, @@ -1375,7 +1446,7 @@ } }, "rightExpression": { - "id": 19, + "id": 23, "kind": "", "startPos": { "offset": 61, @@ -1392,7 +1463,7 @@ "start": 61, "end": 62, "expression": { - "id": 18, + "id": 22, "kind": "", "startPos": { "offset": 61, @@ -1457,7 +1528,7 @@ "args": [] }, { - "id": 31, + "id": 35, "kind": "", "startPos": { "offset": 68, @@ -1474,7 +1545,7 @@ "start": 68, "end": 78, "callee": { - "id": 30, + "id": 34, "kind": "", "startPos": { "offset": 68, @@ -1534,7 +1605,7 @@ "end": 76 }, "leftExpression": { - "id": 27, + "id": 31, "kind": "", "startPos": { "offset": 68, @@ -1594,7 +1665,7 @@ "end": 71 }, "leftExpression": { - "id": 23, + "id": 27, "kind": "", "startPos": { "offset": 68, @@ -1611,7 +1682,7 @@ "start": 68, "end": 69, "expression": { - "id": 22, + "id": 26, "kind": "", "startPos": { "offset": 68, @@ -1758,7 +1829,7 @@ } }, "rightExpression": { - "id": 26, + "id": 30, "kind": "", "startPos": { "offset": 72, @@ -1796,7 +1867,7 @@ "end": 73 }, "expression": { - "id": 25, + "id": 29, "kind": "", "startPos": { "offset": 73, @@ -1813,7 +1884,7 @@ "start": 73, "end": 74, "expression": { - "id": 24, + "id": 28, "kind": "", "startPos": { "offset": 73, @@ -1877,7 +1948,7 @@ } }, "rightExpression": { - "id": 29, + "id": 33, "kind": "", "startPos": { "offset": 77, @@ -1894,7 +1965,7 @@ "start": 77, "end": 78, "expression": { - "id": 28, + "id": 32, "kind": "", "startPos": { "offset": 77, @@ -1959,7 +2030,7 @@ "args": [] }, { - "id": 41, + "id": 45, "kind": "", "startPos": { "offset": 84, @@ -1976,7 +2047,7 @@ "start": 84, "end": 95, "callee": { - "id": 40, + "id": 44, "kind": "", "startPos": { "offset": 84, @@ -2036,7 +2107,7 @@ "end": 93 }, "leftExpression": { - "id": 37, + "id": 41, "kind": "", "startPos": { "offset": 84, @@ -2160,7 +2231,7 @@ }, "elementList": [ { - "id": 36, + "id": 40, "kind": "", "startPos": { "offset": 85, @@ -2220,7 +2291,7 @@ "end": 88 }, "leftExpression": { - "id": 33, + "id": 37, "kind": "", "startPos": { "offset": 85, @@ -2237,7 +2308,7 @@ "start": 85, "end": 86, "expression": { - "id": 32, + "id": 36, "kind": "", "startPos": { "offset": 85, @@ -2299,7 +2370,7 @@ } }, "rightExpression": { - "id": 35, + "id": 39, "kind": "", "startPos": { "offset": 89, @@ -2316,7 +2387,7 @@ "start": 89, "end": 90, "expression": { - "id": 34, + "id": 38, "kind": "", "startPos": { "offset": 89, @@ -2403,7 +2474,7 @@ } }, "rightExpression": { - "id": 39, + "id": 43, "kind": "", "startPos": { "offset": 94, @@ -2420,7 +2491,7 @@ "start": 94, "end": 95, "expression": { - "id": 38, + "id": 42, "kind": "", "startPos": { "offset": 94, @@ -2485,7 +2556,7 @@ "args": [] }, { - "id": 50, + "id": 54, "kind": "", "startPos": { "offset": 101, @@ -2502,7 +2573,7 @@ "start": 101, "end": 114, "callee": { - "id": 49, + "id": 53, "kind": "", "startPos": { "offset": 101, @@ -2562,7 +2633,7 @@ "end": 110 }, "leftExpression": { - "id": 46, + "id": 50, "kind": "", "startPos": { "offset": 101, @@ -2622,7 +2693,7 @@ "end": 104 }, "leftExpression": { - "id": 43, + "id": 47, "kind": "", "startPos": { "offset": 101, @@ -2639,7 +2710,7 @@ "start": 101, "end": 102, "expression": { - "id": 42, + "id": 46, "kind": "", "startPos": { "offset": 101, @@ -2786,7 +2857,7 @@ } }, "rightExpression": { - "id": 45, + "id": 49, "kind": "", "startPos": { "offset": 105, @@ -2803,7 +2874,7 @@ "start": 105, "end": 108, "expression": { - "id": 44, + "id": 48, "kind": "", "startPos": { "offset": 105, @@ -2866,7 +2937,7 @@ } }, "rightExpression": { - "id": 48, + "id": 52, "kind": "", "startPos": { "offset": 111, @@ -2883,7 +2954,7 @@ "start": 111, "end": 114, "expression": { - "id": 47, + "id": 51, "kind": "", "startPos": { "offset": 111, @@ -2948,7 +3019,7 @@ "args": [] }, { - "id": 59, + "id": 63, "kind": "", "startPos": { "offset": 122, @@ -2965,7 +3036,7 @@ "start": 122, "end": 141, "callee": { - "id": 58, + "id": 62, "kind": "", "startPos": { "offset": 122, @@ -3025,7 +3096,7 @@ "end": 134 }, "leftExpression": { - "id": 55, + "id": 59, "kind": "", "startPos": { "offset": 122, @@ -3085,7 +3156,7 @@ "end": 125 }, "leftExpression": { - "id": 52, + "id": 56, "kind": "", "startPos": { "offset": 122, @@ -3102,7 +3173,7 @@ "start": 122, "end": 123, "expression": { - "id": 51, + "id": 55, "kind": "", "startPos": { "offset": 122, @@ -3270,7 +3341,7 @@ } }, "rightExpression": { - "id": 54, + "id": 58, "kind": "", "startPos": { "offset": 131, @@ -3287,7 +3358,7 @@ "start": 131, "end": 132, "expression": { - "id": 53, + "id": 57, "kind": "", "startPos": { "offset": 131, @@ -3435,7 +3506,7 @@ } }, "rightExpression": { - "id": 57, + "id": 61, "kind": "", "startPos": { "offset": 140, @@ -3452,7 +3523,7 @@ "start": 140, "end": 141, "expression": { - "id": 56, + "id": 60, "kind": "", "startPos": { "offset": 140, @@ -3602,7 +3673,7 @@ "args": [] }, { - "id": 68, + "id": 72, "kind": "", "startPos": { "offset": 149, @@ -3619,7 +3690,7 @@ "start": 149, "end": 168, "callee": { - "id": 67, + "id": 71, "kind": "", "startPos": { "offset": 149, @@ -3764,7 +3835,7 @@ "end": 166 }, "leftExpression": { - "id": 64, + "id": 68, "kind": "", "startPos": { "offset": 149, @@ -3909,7 +3980,7 @@ "end": 157 }, "leftExpression": { - "id": 61, + "id": 65, "kind": "", "startPos": { "offset": 149, @@ -3926,7 +3997,7 @@ "start": 149, "end": 150, "expression": { - "id": 60, + "id": 64, "kind": "", "startPos": { "offset": 149, @@ -4094,7 +4165,7 @@ } }, "rightExpression": { - "id": 63, + "id": 67, "kind": "", "startPos": { "offset": 158, @@ -4111,7 +4182,7 @@ "start": 158, "end": 159, "expression": { - "id": 62, + "id": 66, "kind": "", "startPos": { "offset": 158, @@ -4174,7 +4245,7 @@ } }, "rightExpression": { - "id": 66, + "id": 70, "kind": "", "startPos": { "offset": 167, @@ -4191,7 +4262,7 @@ "start": 167, "end": 168, "expression": { - "id": 65, + "id": 69, "kind": "", "startPos": { "offset": 167, @@ -4256,7 +4327,7 @@ "args": [] }, { - "id": 77, + "id": 81, "kind": "", "startPos": { "offset": 176, @@ -4273,7 +4344,7 @@ "start": 176, "end": 181, "callee": { - "id": 76, + "id": 80, "kind": "", "startPos": { "offset": 176, @@ -4311,7 +4382,7 @@ "end": 180 }, "leftExpression": { - "id": 73, + "id": 77, "kind": "", "startPos": { "offset": 176, @@ -4349,7 +4420,7 @@ "end": 178 }, "leftExpression": { - "id": 70, + "id": 74, "kind": "", "startPos": { "offset": 176, @@ -4366,7 +4437,7 @@ "start": 176, "end": 177, "expression": { - "id": 69, + "id": 73, "kind": "", "startPos": { "offset": 176, @@ -4512,7 +4583,7 @@ } }, "rightExpression": { - "id": 72, + "id": 76, "kind": "", "startPos": { "offset": 178, @@ -4529,7 +4600,7 @@ "start": 178, "end": 179, "expression": { - "id": 71, + "id": 75, "kind": "", "startPos": { "offset": 178, @@ -4570,7 +4641,7 @@ } }, "rightExpression": { - "id": 75, + "id": 79, "kind": "", "startPos": { "offset": 180, @@ -4587,7 +4658,7 @@ "start": 180, "end": 181, "expression": { - "id": 74, + "id": 78, "kind": "", "startPos": { "offset": 180, @@ -4652,7 +4723,7 @@ "args": [] }, { - "id": 86, + "id": 90, "kind": "", "startPos": { "offset": 189, @@ -4669,7 +4740,7 @@ "start": 189, "end": 206, "callee": { - "id": 85, + "id": 89, "kind": "", "startPos": { "offset": 189, @@ -4729,7 +4800,7 @@ "end": 199 }, "leftExpression": { - "id": 82, + "id": 86, "kind": "", "startPos": { "offset": 189, @@ -4789,7 +4860,7 @@ "end": 191 }, "leftExpression": { - "id": 79, + "id": 83, "kind": "", "startPos": { "offset": 189, @@ -4806,7 +4877,7 @@ "start": 189, "end": 190, "expression": { - "id": 78, + "id": 82, "kind": "", "startPos": { "offset": 189, @@ -4952,7 +5023,7 @@ } }, "rightExpression": { - "id": 81, + "id": 85, "kind": "", "startPos": { "offset": 197, @@ -4969,7 +5040,7 @@ "start": 197, "end": 198, "expression": { - "id": 80, + "id": 84, "kind": "", "startPos": { "offset": 197, @@ -5095,7 +5166,7 @@ } }, "rightExpression": { - "id": 84, + "id": 88, "kind": "", "startPos": { "offset": 205, @@ -5112,7 +5183,7 @@ "start": 205, "end": 206, "expression": { - "id": 83, + "id": 87, "kind": "", "startPos": { "offset": 205, @@ -5262,7 +5333,7 @@ "args": [] }, { - "id": 95, + "id": 99, "kind": "", "startPos": { "offset": 214, @@ -5279,7 +5350,7 @@ "start": 214, "end": 226, "callee": { - "id": 94, + "id": 98, "kind": "", "startPos": { "offset": 214, @@ -5423,7 +5494,7 @@ "end": 225 }, "leftExpression": { - "id": 91, + "id": 95, "kind": "", "startPos": { "offset": 214, @@ -5461,7 +5532,7 @@ "end": 216 }, "leftExpression": { - "id": 88, + "id": 92, "kind": "", "startPos": { "offset": 214, @@ -5478,7 +5549,7 @@ "start": 214, "end": 215, "expression": { - "id": 87, + "id": 91, "kind": "", "startPos": { "offset": 214, @@ -5624,7 +5695,7 @@ } }, "rightExpression": { - "id": 90, + "id": 94, "kind": "", "startPos": { "offset": 216, @@ -5641,7 +5712,7 @@ "start": 216, "end": 217, "expression": { - "id": 89, + "id": 93, "kind": "", "startPos": { "offset": 216, @@ -5704,7 +5775,7 @@ } }, "rightExpression": { - "id": 93, + "id": 97, "kind": "", "startPos": { "offset": 225, @@ -5721,7 +5792,7 @@ "start": 225, "end": 226, "expression": { - "id": 92, + "id": 96, "kind": "", "startPos": { "offset": 225, @@ -5786,7 +5857,7 @@ "args": [] }, { - "id": 100, + "id": 104, "kind": "", "startPos": { "offset": 234, @@ -5803,7 +5874,7 @@ "start": 234, "end": 237, "callee": { - "id": 99, + "id": 103, "kind": "", "startPos": { "offset": 234, @@ -5820,7 +5891,7 @@ "start": 234, "end": 237, "callee": { - "id": 97, + "id": 101, "kind": "", "startPos": { "offset": 234, @@ -5837,7 +5908,7 @@ "start": 234, "end": 235, "expression": { - "id": 96, + "id": 100, "kind": "", "startPos": { "offset": 234, @@ -5983,7 +6054,7 @@ } }, "argumentList": { - "id": 98, + "id": 102, "kind": "", "startPos": { "offset": 235, @@ -6070,7 +6141,7 @@ "args": [] }, { - "id": 108, + "id": 112, "kind": "", "startPos": { "offset": 245, @@ -6087,7 +6158,7 @@ "start": 245, "end": 254, "callee": { - "id": 107, + "id": 111, "kind": "", "startPos": { "offset": 245, @@ -6232,7 +6303,7 @@ }, "elementList": [ { - "id": 102, + "id": 106, "kind": "", "startPos": { "offset": 246, @@ -6249,7 +6320,7 @@ "start": 246, "end": 247, "expression": { - "id": 101, + "id": 105, "kind": "", "startPos": { "offset": 246, @@ -6289,7 +6360,7 @@ } }, { - "id": 104, + "id": 108, "kind": "", "startPos": { "offset": 249, @@ -6306,7 +6377,7 @@ "start": 249, "end": 250, "expression": { - "id": 103, + "id": 107, "kind": "", "startPos": { "offset": 249, @@ -6346,7 +6417,7 @@ } }, { - "id": 106, + "id": 110, "kind": "", "startPos": { "offset": 252, @@ -6363,7 +6434,7 @@ "start": 252, "end": 253, "expression": { - "id": 105, + "id": 109, "kind": "", "startPos": { "offset": 252, @@ -6538,7 +6609,7 @@ "args": [] }, { - "id": 116, + "id": 120, "kind": "", "startPos": { "offset": 260, @@ -6555,7 +6626,7 @@ "start": 260, "end": 269, "callee": { - "id": 115, + "id": 119, "kind": "", "startPos": { "offset": 260, @@ -6679,7 +6750,7 @@ }, "elementList": [ { - "id": 110, + "id": 114, "kind": "", "startPos": { "offset": 261, @@ -6696,7 +6767,7 @@ "start": 261, "end": 262, "expression": { - "id": 109, + "id": 113, "kind": "", "startPos": { "offset": 261, @@ -6736,7 +6807,7 @@ } }, { - "id": 112, + "id": 116, "kind": "", "startPos": { "offset": 264, @@ -6753,7 +6824,7 @@ "start": 264, "end": 265, "expression": { - "id": 111, + "id": 115, "kind": "", "startPos": { "offset": 264, @@ -6793,7 +6864,7 @@ } }, { - "id": 114, + "id": 118, "kind": "", "startPos": { "offset": 267, @@ -6810,7 +6881,7 @@ "start": 267, "end": 268, "expression": { - "id": 113, + "id": 117, "kind": "", "startPos": { "offset": 267, @@ -6985,7 +7056,7 @@ "args": [] }, { - "id": 128, + "id": 132, "kind": "", "startPos": { "offset": 277, @@ -7002,7 +7073,7 @@ "start": 277, "end": 296, "callee": { - "id": 127, + "id": 131, "kind": "", "startPos": { "offset": 277, @@ -7147,7 +7218,7 @@ }, "elementList": [ { - "id": 126, + "id": 130, "kind": "", "startPos": { "offset": 278, @@ -7164,7 +7235,7 @@ "start": 278, "end": 295, "callee": { - "id": 118, + "id": 122, "kind": "", "startPos": { "offset": 278, @@ -7181,7 +7252,7 @@ "start": 278, "end": 279, "expression": { - "id": 117, + "id": 121, "kind": "", "startPos": { "offset": 278, @@ -7243,7 +7314,7 @@ } }, "argumentList": { - "id": 125, + "id": 129, "kind": "", "startPos": { "offset": 286, @@ -7388,7 +7459,7 @@ }, "elementList": [ { - "id": 120, + "id": 124, "kind": "", "startPos": { "offset": 287, @@ -7405,7 +7476,7 @@ "start": 287, "end": 288, "expression": { - "id": 119, + "id": 123, "kind": "", "startPos": { "offset": 287, @@ -7445,7 +7516,7 @@ } }, { - "id": 122, + "id": 126, "kind": "", "startPos": { "offset": 290, @@ -7462,7 +7533,7 @@ "start": 290, "end": 291, "expression": { - "id": 121, + "id": 125, "kind": "", "startPos": { "offset": 290, @@ -7502,7 +7573,7 @@ } }, { - "id": 124, + "id": 128, "kind": "", "startPos": { "offset": 293, @@ -7519,7 +7590,7 @@ "start": 293, "end": 294, "expression": { - "id": 123, + "id": 127, "kind": "", "startPos": { "offset": 293, @@ -7719,7 +7790,7 @@ "args": [] }, { - "id": 134, + "id": 138, "kind": "", "startPos": { "offset": 304, @@ -7736,7 +7807,7 @@ "start": 304, "end": 316, "callee": { - "id": 133, + "id": 137, "kind": "", "startPos": { "offset": 304, @@ -7881,7 +7952,7 @@ }, "elementList": [ { - "id": 130, + "id": 134, "kind": "", "startPos": { "offset": 305, @@ -7898,7 +7969,7 @@ "start": 305, "end": 306, "expression": { - "id": 129, + "id": 133, "kind": "", "startPos": { "offset": 305, @@ -7938,7 +8009,7 @@ } }, { - "id": 132, + "id": 136, "kind": "", "startPos": { "offset": 314, @@ -7955,7 +8026,7 @@ "start": 314, "end": 315, "expression": { - "id": 131, + "id": 135, "kind": "", "startPos": { "offset": 314, @@ -8193,7 +8264,7 @@ "args": [] }, { - "id": 153, + "id": 157, "kind": "", "startPos": { "offset": 324, @@ -8210,7 +8281,7 @@ "start": 324, "end": 348, "callee": { - "id": 152, + "id": 156, "kind": "", "startPos": { "offset": 324, @@ -8270,7 +8341,7 @@ "end": 336 }, "leftExpression": { - "id": 142, + "id": 146, "kind": "", "startPos": { "offset": 324, @@ -8330,7 +8401,7 @@ "end": 331 }, "leftExpression": { - "id": 139, + "id": 143, "kind": "", "startPos": { "offset": 324, @@ -8390,7 +8461,7 @@ "end": 327 }, "leftExpression": { - "id": 136, + "id": 140, "kind": "", "startPos": { "offset": 324, @@ -8407,7 +8478,7 @@ "start": 324, "end": 325, "expression": { - "id": 135, + "id": 139, "kind": "", "startPos": { "offset": 324, @@ -8575,7 +8646,7 @@ } }, "rightExpression": { - "id": 138, + "id": 142, "kind": "", "startPos": { "offset": 328, @@ -8592,7 +8663,7 @@ "start": 328, "end": 329, "expression": { - "id": 137, + "id": 141, "kind": "", "startPos": { "offset": 328, @@ -8655,7 +8726,7 @@ } }, "rightExpression": { - "id": 141, + "id": 145, "kind": "", "startPos": { "offset": 332, @@ -8672,7 +8743,7 @@ "start": 332, "end": 333, "expression": { - "id": 140, + "id": 144, "kind": "", "startPos": { "offset": 332, @@ -8735,7 +8806,7 @@ } }, "rightExpression": { - "id": 151, + "id": 155, "kind": "", "startPos": { "offset": 337, @@ -8795,7 +8866,7 @@ "end": 340 }, "leftExpression": { - "id": 144, + "id": 148, "kind": "", "startPos": { "offset": 337, @@ -8812,7 +8883,7 @@ "start": 337, "end": 338, "expression": { - "id": 143, + "id": 147, "kind": "", "startPos": { "offset": 337, @@ -8874,7 +8945,7 @@ } }, "rightExpression": { - "id": 150, + "id": 154, "kind": "", "startPos": { "offset": 341, @@ -8913,7 +8984,7 @@ }, "elementList": [ { - "id": 149, + "id": 153, "kind": "", "startPos": { "offset": 342, @@ -8973,7 +9044,7 @@ "end": 345 }, "leftExpression": { - "id": 146, + "id": 150, "kind": "", "startPos": { "offset": 342, @@ -8990,7 +9061,7 @@ "start": 342, "end": 343, "expression": { - "id": 145, + "id": 149, "kind": "", "startPos": { "offset": 342, @@ -9052,7 +9123,7 @@ } }, "rightExpression": { - "id": 148, + "id": 152, "kind": "", "startPos": { "offset": 346, @@ -9069,7 +9140,7 @@ "start": 346, "end": 347, "expression": { - "id": 147, + "id": 151, "kind": "", "startPos": { "offset": 346, @@ -9160,7 +9231,7 @@ "args": [] }, { - "id": 159, + "id": 163, "kind": "", "startPos": { "offset": 356, @@ -9177,7 +9248,7 @@ "start": 356, "end": 362, "callee": { - "id": 158, + "id": 162, "kind": "", "startPos": { "offset": 356, @@ -9237,7 +9308,7 @@ "end": 360 }, "leftExpression": { - "id": 155, + "id": 159, "kind": "", "startPos": { "offset": 356, @@ -9254,7 +9325,7 @@ "start": 356, "end": 357, "expression": { - "id": 154, + "id": 158, "kind": "", "startPos": { "offset": 356, @@ -9422,7 +9493,7 @@ } }, "rightExpression": { - "id": 157, + "id": 161, "kind": "", "startPos": { "offset": 361, @@ -9439,7 +9510,7 @@ "start": 361, "end": 362, "expression": { - "id": 156, + "id": 160, "kind": "", "startPos": { "offset": 361, @@ -9504,7 +9575,7 @@ "args": [] }, { - "id": 171, + "id": 175, "kind": "", "startPos": { "offset": 370, @@ -9521,7 +9592,7 @@ "start": 370, "end": 384, "callee": { - "id": 170, + "id": 174, "kind": "", "startPos": { "offset": 370, @@ -9581,7 +9652,7 @@ "end": 373 }, "leftExpression": { - "id": 161, + "id": 165, "kind": "", "startPos": { "offset": 370, @@ -9598,7 +9669,7 @@ "start": 370, "end": 371, "expression": { - "id": 160, + "id": 164, "kind": "", "startPos": { "offset": 370, @@ -9766,7 +9837,7 @@ } }, "rightExpression": { - "id": 169, + "id": 173, "kind": "", "startPos": { "offset": 374, @@ -9826,7 +9897,7 @@ "end": 378 }, "leftExpression": { - "id": 163, + "id": 167, "kind": "", "startPos": { "offset": 374, @@ -9843,7 +9914,7 @@ "start": 374, "end": 375, "expression": { - "id": 162, + "id": 166, "kind": "", "startPos": { "offset": 374, @@ -9905,7 +9976,7 @@ } }, "rightExpression": { - "id": 168, + "id": 172, "kind": "", "startPos": { "offset": 379, @@ -9965,7 +10036,7 @@ "end": 382 }, "leftExpression": { - "id": 165, + "id": 169, "kind": "", "startPos": { "offset": 379, @@ -9982,7 +10053,7 @@ "start": 379, "end": 380, "expression": { - "id": 164, + "id": 168, "kind": "", "startPos": { "offset": 379, @@ -10044,7 +10115,7 @@ } }, "rightExpression": { - "id": 167, + "id": 171, "kind": "", "startPos": { "offset": 383, @@ -10061,7 +10132,7 @@ "start": 383, "end": 384, "expression": { - "id": 166, + "id": 170, "kind": "", "startPos": { "offset": 383, @@ -10128,7 +10199,7 @@ "args": [] }, { - "id": 180, + "id": 184, "kind": "", "startPos": { "offset": 392, @@ -10145,7 +10216,7 @@ "start": 392, "end": 402, "callee": { - "id": 179, + "id": 183, "kind": "", "startPos": { "offset": 392, @@ -10205,7 +10276,7 @@ "end": 395 }, "leftExpression": { - "id": 173, + "id": 177, "kind": "", "startPos": { "offset": 392, @@ -10222,7 +10293,7 @@ "start": 392, "end": 393, "expression": { - "id": 172, + "id": 176, "kind": "", "startPos": { "offset": 392, @@ -10390,7 +10461,7 @@ } }, "rightExpression": { - "id": 178, + "id": 182, "kind": "", "startPos": { "offset": 396, @@ -10450,7 +10521,7 @@ "end": 400 }, "leftExpression": { - "id": 175, + "id": 179, "kind": "", "startPos": { "offset": 396, @@ -10467,7 +10538,7 @@ "start": 396, "end": 397, "expression": { - "id": 174, + "id": 178, "kind": "", "startPos": { "offset": 396, @@ -10529,7 +10600,7 @@ } }, "rightExpression": { - "id": 177, + "id": 181, "kind": "", "startPos": { "offset": 401, @@ -10546,7 +10617,7 @@ "start": 401, "end": 402, "expression": { - "id": 176, + "id": 180, "kind": "", "startPos": { "offset": 401, @@ -10612,7 +10683,7 @@ "args": [] }, { - "id": 214, + "id": 218, "kind": "", "startPos": { "offset": 410, @@ -10629,7 +10700,7 @@ "start": 410, "end": 458, "callee": { - "id": 213, + "id": 217, "kind": "", "startPos": { "offset": 410, @@ -10689,7 +10760,7 @@ "end": 414 }, "leftExpression": { - "id": 182, + "id": 186, "kind": "", "startPos": { "offset": 410, @@ -10706,7 +10777,7 @@ "start": 410, "end": 411, "expression": { - "id": 181, + "id": 185, "kind": "", "startPos": { "offset": 410, @@ -10874,7 +10945,7 @@ } }, "rightExpression": { - "id": 212, + "id": 216, "kind": "", "startPos": { "offset": 415, @@ -10997,7 +11068,7 @@ "end": 449 }, "leftExpression": { - "id": 201, + "id": 205, "kind": "", "startPos": { "offset": 415, @@ -11141,7 +11212,7 @@ "end": 432 }, "leftExpression": { - "id": 189, + "id": 193, "kind": "", "startPos": { "offset": 415, @@ -11201,7 +11272,7 @@ "end": 418 }, "leftExpression": { - "id": 184, + "id": 188, "kind": "", "startPos": { "offset": 415, @@ -11218,7 +11289,7 @@ "start": 415, "end": 416, "expression": { - "id": 183, + "id": 187, "kind": "", "startPos": { "offset": 415, @@ -11280,7 +11351,7 @@ } }, "rightExpression": { - "id": 188, + "id": 192, "kind": "", "startPos": { "offset": 419, @@ -11297,7 +11368,7 @@ "start": 419, "end": 423, "callee": { - "id": 186, + "id": 190, "kind": "", "startPos": { "offset": 419, @@ -11314,7 +11385,7 @@ "start": 419, "end": 420, "expression": { - "id": 185, + "id": 189, "kind": "", "startPos": { "offset": 419, @@ -11376,7 +11447,7 @@ } }, "argumentList": { - "id": 187, + "id": 191, "kind": "", "startPos": { "offset": 421, @@ -11462,7 +11533,7 @@ } }, "rightExpression": { - "id": 200, + "id": 204, "kind": "", "startPos": { "offset": 432, @@ -11500,7 +11571,7 @@ "end": 433 }, "expression": { - "id": 199, + "id": 203, "kind": "", "startPos": { "offset": 433, @@ -11538,7 +11609,7 @@ "end": 434 }, "expression": { - "id": 198, + "id": 202, "kind": "", "startPos": { "offset": 434, @@ -11576,7 +11647,7 @@ "end": 435 }, "expression": { - "id": 197, + "id": 201, "kind": "", "startPos": { "offset": 435, @@ -11614,7 +11685,7 @@ "end": 436 }, "expression": { - "id": 196, + "id": 200, "kind": "", "startPos": { "offset": 436, @@ -11652,7 +11723,7 @@ "end": 437 }, "expression": { - "id": 195, + "id": 199, "kind": "", "startPos": { "offset": 437, @@ -11690,7 +11761,7 @@ "end": 438 }, "expression": { - "id": 194, + "id": 198, "kind": "", "startPos": { "offset": 438, @@ -11728,7 +11799,7 @@ "end": 439 }, "expression": { - "id": 193, + "id": 197, "kind": "", "startPos": { "offset": 439, @@ -11766,7 +11837,7 @@ "end": 440 }, "expression": { - "id": 192, + "id": 196, "kind": "", "startPos": { "offset": 440, @@ -11804,7 +11875,7 @@ "end": 441 }, "expression": { - "id": 191, + "id": 195, "kind": "", "startPos": { "offset": 441, @@ -11821,7 +11892,7 @@ "start": 441, "end": 442, "expression": { - "id": 190, + "id": 194, "kind": "", "startPos": { "offset": 441, @@ -11893,7 +11964,7 @@ } }, "rightExpression": { - "id": 211, + "id": 215, "kind": "", "startPos": { "offset": 449, @@ -11931,7 +12002,7 @@ "end": 450 }, "expression": { - "id": 210, + "id": 214, "kind": "", "startPos": { "offset": 450, @@ -11969,7 +12040,7 @@ "end": 451 }, "expression": { - "id": 209, + "id": 213, "kind": "", "startPos": { "offset": 451, @@ -12007,7 +12078,7 @@ "end": 452 }, "expression": { - "id": 208, + "id": 212, "kind": "", "startPos": { "offset": 452, @@ -12045,7 +12116,7 @@ "end": 453 }, "expression": { - "id": 207, + "id": 211, "kind": "", "startPos": { "offset": 453, @@ -12083,7 +12154,7 @@ "end": 454 }, "expression": { - "id": 206, + "id": 210, "kind": "", "startPos": { "offset": 454, @@ -12121,7 +12192,7 @@ "end": 455 }, "expression": { - "id": 205, + "id": 209, "kind": "", "startPos": { "offset": 455, @@ -12159,7 +12230,7 @@ "end": 456 }, "expression": { - "id": 204, + "id": 208, "kind": "", "startPos": { "offset": 456, @@ -12197,7 +12268,7 @@ "end": 457 }, "expression": { - "id": 203, + "id": 207, "kind": "", "startPos": { "offset": 457, @@ -12214,7 +12285,7 @@ "start": 457, "end": 458, "expression": { - "id": 202, + "id": 206, "kind": "", "startPos": { "offset": 457, @@ -12356,119 +12427,5 @@ "end": 463 } }, - "errors": [ - { - "code": 1008, - "diagnostic": "Unexpected '*' in an expression", - "nodeOrToken": { - "kind": "", - "startPos": { - "offset": 23, - "line": 1, - "column": 4 - }, - "endPos": { - "offset": 24, - "line": 1, - "column": 5 - }, - "value": "*", - "leadingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 19, - "line": 1, - "column": 0 - }, - "endPos": { - "offset": 20, - "line": 1, - "column": 1 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 19, - "end": 20 - }, - { - "kind": "", - "startPos": { - "offset": 20, - "line": 1, - "column": 1 - }, - "endPos": { - "offset": 21, - "line": 1, - "column": 2 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 20, - "end": 21 - }, - { - "kind": "", - "startPos": { - "offset": 21, - "line": 1, - "column": 2 - }, - "endPos": { - "offset": 22, - "line": 1, - "column": 3 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 21, - "end": 22 - }, - { - "kind": "", - "startPos": { - "offset": 22, - "line": 1, - "column": 3 - }, - "endPos": { - "offset": 23, - "line": 1, - "column": 4 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 22, - "end": 23 - } - ], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 23, - "end": 24 - }, - "start": 23, - "end": 24, - "name": "CompileError" - } - ] + "errors": [] } \ No newline at end of file diff --git a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts index ee737e5b4..2d6f895e7 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts @@ -2,6 +2,7 @@ import { applyTextEdits, TextEdit } from './applyTextEdits'; import Lexer from '@/core/lexer/lexer'; import Parser from '@/core/parser/parser'; import { SyntaxNodeIdGenerator } from '@/core/parser/nodes'; +import { destructureComplexVariable } from '@/core/analyzer/utils'; export interface DiagramViewSyncOperation { operation: 'create' | 'update' | 'delete'; @@ -34,7 +35,10 @@ function findDiagramViewBlocks (source: string): DiagramViewBlock[] { for (const element of program.body) { if (element.type?.value === 'DiagramView') { - const name = element.name?.toString() || ''; + const fragments = element.name + ? destructureComplexVariable(element.name).unwrap_or([]) + : []; + const name = fragments.length > 0 ? fragments[fragments.length - 1] : ''; blocks.push({ name, startIndex: element.start, @@ -46,11 +50,22 @@ function findDiagramViewBlocks (source: string): DiagramViewBlock[] { return blocks; } +/** Returns true if the name requires double-quote wrapping in DBML. */ +function needsQuoting (name: string): boolean { + return !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name); +} + +/** Wraps name in double quotes and escapes internal double quotes if needed. */ +function quoteName (name: string): string { + if (!needsQuoting(name)) return name; + return `"${name.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`; +} + function generateDiagramViewBlock ( name: string, visibleEntities: DiagramViewSyncOperation['visibleEntities'], ): string { - const lines: string[] = [`DiagramView ${name} {`]; + const lines: string[] = [`DiagramView ${quoteName(name)} {`]; // Tables if (visibleEntities?.tables !== undefined) { @@ -149,6 +164,13 @@ function applyOperation (dbml: string, operation: DiagramViewSyncOperation): str } function applyCreate (dbml: string, operation: DiagramViewSyncOperation): string { + // If a block with this name already exists, treat as update to avoid duplicate blocks + const blocks = findDiagramViewBlocks(dbml); + const existing = blocks.find((b) => b.name === operation.name); + if (existing) { + return applyUpdate(dbml, operation, blocks); + } + const newBlock = generateDiagramViewBlock(operation.name, operation.visibleEntities); // Append at end of file diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts index 3e645ee23..9d44565cd 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts @@ -227,6 +227,9 @@ export default class DiagramViewValidator implements ElementValidator { for (const field of fields) { if (field.callee && isExpressionAVariableNode(field.callee)) { + // Wildcards are per-sub-block and don't need uniqueness tracking + if (isWildcardExpression(field.callee)) continue; + const fieldName = extractVarNameFromPrimaryVariable(field.callee).unwrap(); const fieldId = createDiagramViewFieldSymbolIndex(fieldName); diff --git a/packages/dbml-parse/src/core/parser/parser.ts b/packages/dbml-parse/src/core/parser/parser.ts index 60d8c6251..fd2657b33 100644 --- a/packages/dbml-parse/src/core/parser/parser.ts +++ b/packages/dbml-parse/src/core/parser/parser.ts @@ -656,7 +656,14 @@ export default class Parser { private leftExpression_bp (): NormalExpressionNode { let leftExpression: NormalExpressionNode | undefined; - if (isOpToken(this.peek())) { + // Allow * as a wildcard identifier (e.g. DiagramView Tables { * }) + // Must be handled before the isOpToken branch to avoid "Unexpected '*'" error + if (this.check(SyntaxTokenKind.OP) && this.peek().value === '*') { + this.advance(); + leftExpression = this.nodeFactory.create(PrimaryExpressionNode, { + expression: this.nodeFactory.create(VariableNode, { variable: this.previous() }), + }); + } else if (isOpToken(this.peek())) { const args: { op?: SyntaxToken; expression?: NormalExpressionNode; From 66fb313a13b670d94c7234fafa1ec6c385647a09 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Fri, 13 Mar 2026 19:49:35 +0700 Subject: [PATCH 09/31] fix: allow wildcard {*} at DiagramView body level in validator Co-Authored-By: Claude Sonnet 4.6 --- .../examples/validator/validator.test.ts | 44 +++++++++++++++++++ .../elementValidators/diagramView.ts | 4 ++ 2 files changed, 48 insertions(+) diff --git a/packages/dbml-parse/__tests__/examples/validator/validator.test.ts b/packages/dbml-parse/__tests__/examples/validator/validator.test.ts index 316cbff3e..c48ba37fa 100644 --- a/packages/dbml-parse/__tests__/examples/validator/validator.test.ts +++ b/packages/dbml-parse/__tests__/examples/validator/validator.test.ts @@ -1018,6 +1018,50 @@ Table users { name varchar }`; }); }); + describe('DiagramView validation', () => { + test('should accept DiagramView with body-level wildcard {*}', () => { + const source = 'DiagramView name { * }'; + const errors = analyze(source).getErrors(); + + expect(errors).toHaveLength(0); + }); + + test('should accept DiagramView with Tables sub-block', () => { + const source = ` + Table users { id int } + DiagramView name { + Tables { + users + } + } + `; + const errors = analyze(source).getErrors(); + + expect(errors).toHaveLength(0); + }); + + test('should reject DiagramView body-level non-wildcard field', () => { + const source = ` + Table users { id int } + DiagramView name { + users + } + `; + const errors = analyze(source).getErrors(); + + expect(errors).toHaveLength(1); + expect(errors[0].code).toBe(CompileErrorCode.INVALID_DIAGRAMVIEW_FIELD); + }); + + test('should reject DiagramView without a name', () => { + const source = 'DiagramView { * }'; + const errors = analyze(source).getErrors(); + + expect(errors).toHaveLength(1); + expect(errors[0].code).toBe(CompileErrorCode.NAME_NOT_FOUND); + }); + }); + describe('error message quality', () => { test('should include entity name in duplicate error messages', () => { const source = ` diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts index 9d44565cd..a3d383fb0 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts @@ -144,6 +144,10 @@ export default class DiagramViewValidator implements ElementValidator { validateFields (fields: FunctionApplicationNode[]): (CompileError | CompileWarning)[] { return fields.flatMap((field) => { + // Body-level {*} is valid shorthand for "show all entities" + if (field instanceof FunctionApplicationNode && isWildcardExpression(field.callee)) { + return []; + } const errors: (CompileError | CompileWarning)[] = []; // Fields at the top level of DiagramView are not allowed errors.push(new CompileError( From 8be5262ae0bd47b7d5766f92338424b3cae513cb Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Fri, 13 Mar 2026 19:54:54 +0700 Subject: [PATCH 10/31] fix: remove redundant instanceof check in validateFields wildcard guard Co-Authored-By: Claude Sonnet 4.6 --- .../core/analyzer/validator/elementValidators/diagramView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts index a3d383fb0..0af3ffb36 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts @@ -145,7 +145,7 @@ export default class DiagramViewValidator implements ElementValidator { validateFields (fields: FunctionApplicationNode[]): (CompileError | CompileWarning)[] { return fields.flatMap((field) => { // Body-level {*} is valid shorthand for "show all entities" - if (field instanceof FunctionApplicationNode && isWildcardExpression(field.callee)) { + if (isWildcardExpression(field.callee)) { return []; } const errors: (CompileError | CompileWarning)[] = []; From 8dd25ce1bfde2050e036131d0e5c5ba1761524f4 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Fri, 13 Mar 2026 20:06:04 +0700 Subject: [PATCH 11/31] fix: apply Trinity omit-as-show-all rule in DiagramView interpreter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When at least one Trinity dimension (tables/tableGroups/schemas) is explicitly specified with a non-null value in a DiagramView body, omitted Trinity dims are promoted from null → [] (show all). The Notes dimension remains independent and stays null when omitted. Co-Authored-By: Claude Sonnet 4.6 --- .../examples/interpreter/interpreter.test.ts | 78 +++++++++++++++++++ .../elementInterpreter/diagramView.ts | 26 +++++-- 2 files changed, 96 insertions(+), 8 deletions(-) diff --git a/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts b/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts index ea03b464b..873baffaf 100644 --- a/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts +++ b/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts @@ -985,6 +985,84 @@ describe('[example] interpreter', () => { }); }); + describe('DiagramView interpretation (Trinity omit rule)', () => { + test('should apply Trinity rule: Tables explicit → tableGroups and schemas default to []', () => { + const source = ` + Table users { id int } + DiagramView myView { + Tables { users } + } + `; + const db = interpret(source).getValue()!; + + expect(db.diagramViews).toHaveLength(1); + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tables).toEqual([{ name: 'users', schemaName: 'public' }]); + expect(ve.tableGroups).toEqual([]); + expect(ve.schemas).toEqual([]); + expect(ve.stickyNotes).toBeNull(); + }); + + test('should apply Trinity rule: Tables {*} → tableGroups and schemas default to []', () => { + const source = ` + DiagramView myView { + Tables { * } + } + `; + const db = interpret(source).getValue()!; + + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tables).toEqual([]); + expect(ve.tableGroups).toEqual([]); + expect(ve.schemas).toEqual([]); + expect(ve.stickyNotes).toBeNull(); + }); + + test('should apply Trinity rule: Tables explicit + Notes explicit → tableGroups/schemas default to [], stickyNotes is []', () => { + const source = ` + Table users { id int } + DiagramView myView { + Tables { users } + Notes { * } + } + `; + const db = interpret(source).getValue()!; + + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tables).toEqual([{ name: 'users', schemaName: 'public' }]); + expect(ve.tableGroups).toEqual([]); + expect(ve.schemas).toEqual([]); + expect(ve.stickyNotes).toEqual([]); + }); + + test('should produce all null when body is empty (no Trinity non-null)', () => { + const source = ` + DiagramView myView { + } + `; + const db = interpret(source).getValue()!; + + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tables).toBeNull(); + expect(ve.tableGroups).toBeNull(); + expect(ve.schemas).toBeNull(); + expect(ve.stickyNotes).toBeNull(); + }); + + test('should produce all [] when body-level wildcard {*} is used', () => { + const source = ` + DiagramView myView { * } + `; + const db = interpret(source).getValue()!; + + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tables).toEqual([]); + expect(ve.tableGroups).toEqual([]); + expect(ve.schemas).toEqual([]); + expect(ve.stickyNotes).toEqual([]); + }); + }); + describe('standalone note interpretation', () => { test('should interpret standalone note', () => { const source = ` diff --git a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts index f5303232f..c61f6884f 100644 --- a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts +++ b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts @@ -70,30 +70,40 @@ export class DiagramViewInterpreter implements ElementInterpreter { } private interpretBody (body: BlockExpressionNode): CompileError[] { - // Check for shorthand { * } + // Body-level {*} shorthand: show all entities including Notes if (body.body.length === 1) { const first = body.body[0]; if (first instanceof FunctionApplicationNode && isWildcardExpression(first.callee)) { - // Show all entities - this.diagramView.visibleEntities = { - tables: [], - stickyNotes: [], - tableGroups: [], - schemas: [], - }; + this.diagramView.visibleEntities = { tables: [], stickyNotes: [], tableGroups: [], schemas: [] }; return []; } } const [, subs] = partition(body.body, (e) => e instanceof FunctionApplicationNode); + const explicitlySet = new Set(); for (const sub of subs as ElementDeclarationNode[]) { const blockType = sub.type?.value.toLowerCase(); + if (blockType) explicitlySet.add(blockType); if (sub.body instanceof BlockExpressionNode) { this.interpretSubBlock(sub.body, blockType); } } + // Trinity omit rule: if any Trinity dim was explicitly set with a non-null value, + // promote omitted Trinity dims from null → [] (show all) + const ve = this.diagramView.visibleEntities!; + const trinityHasNonNull = + (explicitlySet.has('tables') && ve.tables !== null) || + (explicitlySet.has('tablegroups') && ve.tableGroups !== null) || + (explicitlySet.has('schemas') && ve.schemas !== null); + + if (trinityHasNonNull) { + if (!explicitlySet.has('tables')) ve.tables = []; + if (!explicitlySet.has('tablegroups')) ve.tableGroups = []; + if (!explicitlySet.has('schemas')) ve.schemas = []; + } + return []; } From 187713409998f936be95c8b749f0819fe65df4ca Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Fri, 13 Mar 2026 21:11:42 +0700 Subject: [PATCH 12/31] fix: correct partition predicate, fix lint, add edge case tests for Trinity omit rule --- .../examples/interpreter/interpreter.test.ts | 30 +++++++++++++++++++ .../elementInterpreter/diagramView.ts | 10 +++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts b/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts index 873baffaf..a1737fe17 100644 --- a/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts +++ b/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts @@ -1061,6 +1061,36 @@ describe('[example] interpreter', () => { expect(ve.schemas).toEqual([]); expect(ve.stickyNotes).toEqual([]); }); + + test('should NOT apply Trinity rule when Tables {} is explicitly empty (null stays null)', () => { + const source = ` + DiagramView myView { + Tables {} + } + `; + const db = interpret(source).getValue()!; + + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tables).toBeNull(); + expect(ve.tableGroups).toBeNull(); + expect(ve.schemas).toBeNull(); + expect(ve.stickyNotes).toBeNull(); + }); + + test('should apply Trinity rule: TableGroups {*} as sole trigger → tables and schemas default to []', () => { + const source = ` + DiagramView myView { + TableGroups { * } + } + `; + const db = interpret(source).getValue()!; + + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tables).toEqual([]); + expect(ve.tableGroups).toEqual([]); + expect(ve.schemas).toEqual([]); + expect(ve.stickyNotes).toBeNull(); + }); }); describe('standalone note interpretation', () => { diff --git a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts index c61f6884f..ecb0b4fee 100644 --- a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts +++ b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts @@ -2,7 +2,7 @@ import { partition } from 'lodash-es'; import { destructureComplexVariable } from '@/core/analyzer/utils'; import { CompileError } from '@/core/errors'; import { BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, PrimaryExpressionNode, SyntaxNode, VariableNode } from '@/core/parser/nodes'; -import { ElementInterpreter, InterpreterDatabase, DiagramView, FilterConfig } from '@/core/interpreter/types'; +import { ElementInterpreter, InterpreterDatabase, DiagramView } from '@/core/interpreter/types'; import { getTokenPosition } from '@/core/interpreter/utils'; import { DEFAULT_SCHEMA_NAME } from '@/constants'; @@ -79,7 +79,7 @@ export class DiagramViewInterpreter implements ElementInterpreter { } } - const [, subs] = partition(body.body, (e) => e instanceof FunctionApplicationNode); + const [subs] = partition(body.body, (e) => e instanceof ElementDeclarationNode); const explicitlySet = new Set(); for (const sub of subs as ElementDeclarationNode[]) { @@ -94,9 +94,9 @@ export class DiagramViewInterpreter implements ElementInterpreter { // promote omitted Trinity dims from null → [] (show all) const ve = this.diagramView.visibleEntities!; const trinityHasNonNull = - (explicitlySet.has('tables') && ve.tables !== null) || - (explicitlySet.has('tablegroups') && ve.tableGroups !== null) || - (explicitlySet.has('schemas') && ve.schemas !== null); + (explicitlySet.has('tables') && ve.tables !== null) + || (explicitlySet.has('tablegroups') && ve.tableGroups !== null) + || (explicitlySet.has('schemas') && ve.schemas !== null); if (trinityHasNonNull) { if (!explicitlySet.has('tables')) ve.tables = []; From b19181019380144c7d1129281996a4c64fdd5d67 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Fri, 13 Mar 2026 21:11:46 +0700 Subject: [PATCH 13/31] fix: export syncDiagramView, DiagramView, DiagramViewSyncOperation, FilterConfig from dbml-core types --- packages/dbml-core/types/index.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/dbml-core/types/index.d.ts b/packages/dbml-core/types/index.d.ts index 515349b82..acf6e5bd2 100644 --- a/packages/dbml-core/types/index.d.ts +++ b/packages/dbml-core/types/index.d.ts @@ -36,4 +36,6 @@ export { tryExtractEnum, addDoubleQuoteIfNeeded, formatRecordValue, + syncDiagramView, } from '@dbml/parse'; +export type { DiagramView, DiagramViewSyncOperation, FilterConfig } from '@dbml/parse'; From 98241418a9c8c9a95c291824f9bd1bb0468a3250 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Sun, 15 Mar 2026 03:49:40 +0700 Subject: [PATCH 14/31] Fix sticky notes issues --- .../src/core/analyzer/symbol/symbolIndex.ts | 2 ++ .../dbml-parse/src/core/analyzer/symbol/symbols.ts | 14 ++++++++++++++ .../dbml-parse/src/core/analyzer/symbol/utils.ts | 4 ++++ .../analyzer/validator/elementValidators/note.ts | 4 +++- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/packages/dbml-parse/src/core/analyzer/symbol/symbolIndex.ts b/packages/dbml-parse/src/core/analyzer/symbol/symbolIndex.ts index 6ae7d4082..66f38d9d3 100644 --- a/packages/dbml-parse/src/core/analyzer/symbol/symbolIndex.ts +++ b/packages/dbml-parse/src/core/analyzer/symbol/symbolIndex.ts @@ -82,6 +82,8 @@ export function createNodeSymbolIndex (key: string, symbolKind: SymbolKind): Nod return createTableGroupSymbolIndex(key); case SymbolKind.TableGroupField: return createTableGroupFieldSymbolIndex(key); + case SymbolKind.Note: + return createStickyNoteSymbolIndex(key); case SymbolKind.TablePartial: return createTablePartialSymbolIndex(key); case SymbolKind.PartialInjection: diff --git a/packages/dbml-parse/src/core/analyzer/symbol/symbols.ts b/packages/dbml-parse/src/core/analyzer/symbol/symbols.ts index 9f87b2d80..616da0e2e 100644 --- a/packages/dbml-parse/src/core/analyzer/symbol/symbols.ts +++ b/packages/dbml-parse/src/core/analyzer/symbol/symbols.ts @@ -171,6 +171,20 @@ export class DiagramViewFieldSymbol implements NodeSymbol { } } +// A symbol for a sticky note (top-level Note element) +export class StickyNoteSymbol implements NodeSymbol { + id: NodeSymbolId; + + declaration: SyntaxNode; + + references: SyntaxNode[] = []; + + constructor ({ declaration }: { declaration: SyntaxNode }, id: NodeSymbolId) { + this.id = id; + this.declaration = declaration; + } +} + // A symbol for a table partial, contains the table partial's symbol table // which is used to hold all the column symbols of the table partial export class TablePartialSymbol implements NodeSymbol { diff --git a/packages/dbml-parse/src/core/analyzer/symbol/utils.ts b/packages/dbml-parse/src/core/analyzer/symbol/utils.ts index 6a958c23d..8ab83d913 100644 --- a/packages/dbml-parse/src/core/analyzer/symbol/utils.ts +++ b/packages/dbml-parse/src/core/analyzer/symbol/utils.ts @@ -23,6 +23,7 @@ import { EnumFieldSymbol, TablePartialSymbol, PartialInjectionSymbol, + StickyNoteSymbol, } from './symbols'; // Given `name`, generate indexes with `name` and all possible kind @@ -72,5 +73,8 @@ export function getSymbolKind (symbol: NodeSymbol): SymbolKind { if (symbol instanceof PartialInjectionSymbol) { return SymbolKind.PartialInjection; } + if (symbol instanceof StickyNoteSymbol) { + return SymbolKind.Note; + } throw new Error('No other possible symbol kind in getSymbolKind'); } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/note.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/note.ts index a483e59d1..a4993ce81 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/note.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/note.ts @@ -12,6 +12,7 @@ import SymbolTable from '@/core/analyzer/symbol/symbolTable'; import { ElementKind } from '@/core/analyzer/types'; import { destructureComplexVariable, getElementKind } from '@/core/analyzer/utils'; import { createStickyNoteSymbolIndex } from '@/core/analyzer/symbol/symbolIndex'; +import { StickyNoteSymbol } from '@/core/analyzer/symbol/symbols'; export default class NoteValidator implements ElementValidator { private declarationNode: ElementDeclarationNode & { type: SyntaxToken }; @@ -82,7 +83,8 @@ export default class NoteValidator implements ElementValidator { return [new CompileError(CompileErrorCode.DUPLICATE_NAME, `Sticky note "${trueName}" has already been defined`, nameNode)]; } - this.publicSymbolTable.set(noteId, this.declarationNode.symbol!); + this.declarationNode.symbol = this.symbolFactory.create(StickyNoteSymbol, { declaration: this.declarationNode }); + this.publicSymbolTable.set(noteId, this.declarationNode.symbol); return []; } From 4b217c87bd1c85a429a8633e978d25ac30a52e31 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Wed, 18 Mar 2026 16:43:27 +0700 Subject: [PATCH 15/31] Fix parsed result bugs --- .../examples/interpreter/interpreter.test.ts | 89 +++++++++++++++++++ .../elementInterpreter/diagramView.ts | 14 +++ .../src/core/interpreter/interpreter.ts | 72 +++++++++++++++ .../dbml-parse/src/core/interpreter/types.ts | 4 + 4 files changed, 179 insertions(+) diff --git a/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts b/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts index a1737fe17..0a12db0f9 100644 --- a/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts +++ b/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts @@ -1093,6 +1093,95 @@ describe('[example] interpreter', () => { }); }); + describe('DiagramView wildcard expansion for TableGroups', () => { + test('should expand explicit TableGroups {*} to concrete group names', () => { + const source = ` + Table users { id int } + Table posts { id int } + TableGroup auth_tables { users } + TableGroup content_tables { posts } + DiagramView myView { + TableGroups { * } + } + `; + const db = interpret(source).getValue()!; + + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tableGroups).toEqual([ + { name: 'auth_tables' }, + { name: 'content_tables' }, + ]); + // Trinity rule still applies for tables/schemas (promoted to []) + expect(ve.tables).toEqual([]); + expect(ve.schemas).toEqual([]); + expect(ve.stickyNotes).toBeNull(); + }); + + test('should NOT expand TableGroups {*} in body-level wildcard {*} (all dims are set)', () => { + const source = ` + Table users { id int } + TableGroup auth_tables { users } + DiagramView myView { * } + `; + const db = interpret(source).getValue()!; + + const ve = db.diagramViews[0].visibleEntities; + // Body-level {*} sets all dims — Tables/Schemas are also set, so no expansion + expect(ve.tableGroups).toEqual([]); + expect(ve.tables).toEqual([]); + expect(ve.schemas).toEqual([]); + expect(ve.stickyNotes).toEqual([]); + }); + + test('should NOT expand tableGroups [] from Trinity promotion', () => { + const source = ` + Table users { id int } + TableGroup auth_tables { users } + DiagramView myView { + Tables { users } + } + `; + const db = interpret(source).getValue()!; + + const ve = db.diagramViews[0].visibleEntities; + // tableGroups [] comes from Trinity promotion, not explicit wildcard — should stay [] + expect(ve.tableGroups).toEqual([]); + expect(ve.tables).toEqual([{ name: 'users', schemaName: 'public' }]); + expect(ve.schemas).toEqual([]); + }); + + test('should NOT expand TableGroups {*} when Tables is also explicitly set', () => { + const source = ` + Table users { id int } + TableGroup auth_tables { users } + DiagramView myView { + Tables { users } + TableGroups { * } + } + `; + const db = interpret(source).getValue()!; + + const ve = db.diagramViews[0].visibleEntities; + // Tables is explicitly set, so TableGroups wildcard stays as [] + expect(ve.tableGroups).toEqual([]); + expect(ve.tables).toEqual([{ name: 'users', schemaName: 'public' }]); + expect(ve.schemas).toEqual([]); + }); + + test('should return empty list when TableGroups {*} but no groups exist', () => { + const source = ` + Table users { id int } + DiagramView myView { + TableGroups { * } + } + `; + const db = interpret(source).getValue()!; + + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tableGroups).toEqual([]); + }); + }); + describe('standalone note interpretation', () => { test('should interpret standalone note', () => { const source = ` diff --git a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts index ecb0b4fee..9cfbee781 100644 --- a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts +++ b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts @@ -32,6 +32,8 @@ export class DiagramViewInterpreter implements ElementInterpreter { tableGroups: null, schemas: null, }, + _explicitWildcards: new Set(), + _explicitlySet: new Set(), }; } @@ -75,6 +77,8 @@ export class DiagramViewInterpreter implements ElementInterpreter { const first = body.body[0]; if (first instanceof FunctionApplicationNode && isWildcardExpression(first.callee)) { this.diagramView.visibleEntities = { tables: [], stickyNotes: [], tableGroups: [], schemas: [] }; + this.diagramView._explicitWildcards = new Set(['tables', 'stickyNotes', 'tableGroups', 'schemas']); + this.diagramView._explicitlySet = new Set(['tables', 'stickyNotes', 'tableGroups', 'schemas']); return []; } } @@ -104,6 +108,12 @@ export class DiagramViewInterpreter implements ElementInterpreter { if (!explicitlySet.has('schemas')) ve.schemas = []; } + // Store which dims were explicitly declared (normalize block type names to FilterConfig keys) + if (explicitlySet.has('tables')) this.diagramView._explicitlySet!.add('tables'); + if (explicitlySet.has('tablegroups')) this.diagramView._explicitlySet!.add('tableGroups'); + if (explicitlySet.has('schemas')) this.diagramView._explicitlySet!.add('schemas'); + if (explicitlySet.has('notes')) this.diagramView._explicitlySet!.add('stickyNotes'); + return []; } @@ -120,15 +130,19 @@ export class DiagramViewInterpreter implements ElementInterpreter { switch (blockType) { case 'tables': this.diagramView.visibleEntities!.tables = []; + this.diagramView._explicitWildcards!.add('tables'); break; case 'notes': this.diagramView.visibleEntities!.stickyNotes = []; + this.diagramView._explicitWildcards!.add('stickyNotes'); break; case 'tablegroups': this.diagramView.visibleEntities!.tableGroups = []; + this.diagramView._explicitWildcards!.add('tableGroups'); break; case 'schemas': this.diagramView.visibleEntities!.schemas = []; + this.diagramView._explicitWildcards!.add('schemas'); break; } return; diff --git a/packages/dbml-parse/src/core/interpreter/interpreter.ts b/packages/dbml-parse/src/core/interpreter/interpreter.ts index 90a3ddffb..b82e7a2c6 100644 --- a/packages/dbml-parse/src/core/interpreter/interpreter.ts +++ b/packages/dbml-parse/src/core/interpreter/interpreter.ts @@ -13,6 +13,7 @@ import Report from '@/core/report'; import { getElementKind } from '@/core/analyzer/utils'; import { ElementKind } from '@/core/analyzer/types'; import { CompileWarning } from '../errors'; +import { DEFAULT_SCHEMA_NAME } from '@/constants'; function processColumnInDb (table: T): T { return { @@ -29,6 +30,73 @@ function processColumnInDb (table: T): T { }; } +/** + * Entity types whose explicit wildcard (*) must be expanded to the concrete list of names. + * These types have no "show all" sentinel in FilterConfig — consumers expect + * an explicit list. Add more types here as needed. + */ +const WILDCARD_EXPAND_ENTITIES = new Set(['tableGroups']); + +/** + * Expand explicit wildcard ([]) in DiagramView visibleEntities to the actual list of + * entities for the types listed in WILDCARD_EXPAND_ENTITIES. + * + * Only expands when: + * 1. The user wrote `{ * }` for that entity type (tracked via _explicitWildcards) + * 2. The other Trinity dims (Tables, Schemas) are NOT explicitly set — + * i.e. the wildcard entity is the only Trinity dim declared. + * When other Trinity dims are also declared, [] keeps its "show all" meaning. + */ +function expandDiagramViewWildcards (env: InterpreterDatabase): void { + if (!env.diagramViews) return; + + for (const view of env.diagramViews.values()) { + const ve = view.visibleEntities; + const wildcards = view._explicitWildcards; + const explicitlySet = view._explicitlySet; + if (!wildcards || !explicitlySet) continue; + + if (WILDCARD_EXPAND_ENTITIES.has('tables') && wildcards.has('tables') && ve.tables && ve.tables.length === 0) { + const otherTrinitySet = explicitlySet.has('tableGroups') || explicitlySet.has('schemas'); + if (!otherTrinitySet) { + ve.tables = Array.from(env.tables.values()).map((t) => ({ + name: t.name, + schemaName: t.schemaName || DEFAULT_SCHEMA_NAME, + })); + } + } + + if (WILDCARD_EXPAND_ENTITIES.has('tableGroups') && wildcards.has('tableGroups') && ve.tableGroups && ve.tableGroups.length === 0) { + const otherTrinitySet = explicitlySet.has('tables') || explicitlySet.has('schemas'); + if (!otherTrinitySet) { + ve.tableGroups = Array.from(env.tableGroups.values()).map((tg) => ({ + name: tg.name!, + })); + } + } + + if (WILDCARD_EXPAND_ENTITIES.has('stickyNotes') && wildcards.has('stickyNotes') && ve.stickyNotes && ve.stickyNotes.length === 0) { + // stickyNotes is not part of Trinity, no other-dim check needed + ve.stickyNotes = Array.from(env.notes.values()).map((n) => ({ + name: n.name, + })); + } + + if (WILDCARD_EXPAND_ENTITIES.has('schemas') && wildcards.has('schemas') && ve.schemas && ve.schemas.length === 0) { + const otherTrinitySet = explicitlySet.has('tables') || explicitlySet.has('tableGroups'); + if (!otherTrinitySet) { + ve.schemas = [...new Set( + Array.from(env.tables.values()).map((t) => t.schemaName || DEFAULT_SCHEMA_NAME), + )].map((name) => ({ name })); + } + } + + // Clean up internal markers before output + delete view._explicitWildcards; + delete view._explicitlySet; + } +} + function convertEnvToDb (env: InterpreterDatabase): Database { // Convert records Map to array of TableRecord const records: TableRecord[] = []; @@ -132,6 +200,10 @@ export default class Interpreter { warnings.push(...recordsResult.getWarnings()); } + // Post-processing: expand wildcards in DiagramView visibleEntities + // At this point all tables, tableGroups, notes are fully interpreted + expandDiagramViewWildcards(this.env); + return new Report(convertEnvToDb(this.env), errors, warnings); } } diff --git a/packages/dbml-parse/src/core/interpreter/types.ts b/packages/dbml-parse/src/core/interpreter/types.ts index 68a89b154..01d6db7d5 100644 --- a/packages/dbml-parse/src/core/interpreter/types.ts +++ b/packages/dbml-parse/src/core/interpreter/types.ts @@ -29,6 +29,10 @@ export interface DiagramView { schemaName: string | null; visibleEntities: FilterConfig; token: TokenPosition; + /** Internal: tracks which dims used explicit wildcard (*). Stripped before output. */ + _explicitWildcards?: Set; + /** Internal: tracks which dims were explicitly declared in the DBML. Stripped before output. */ + _explicitlySet?: Set; } export interface InterpreterDatabase { From 83f4886d8bffa9cafc3d6757e032e745205c3d04 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Wed, 18 Mar 2026 16:43:59 +0700 Subject: [PATCH 16/31] Setup GitNexus --- .claude/skills/gitnexus/gitnexus-cli/SKILL.md | 82 ++++++++++++ .../gitnexus/gitnexus-debugging/SKILL.md | 89 +++++++++++++ .../gitnexus/gitnexus-exploring/SKILL.md | 78 +++++++++++ .../skills/gitnexus/gitnexus-guide/SKILL.md | 64 +++++++++ .../gitnexus-impact-analysis/SKILL.md | 97 ++++++++++++++ .../gitnexus/gitnexus-refactoring/SKILL.md | 121 ++++++++++++++++++ .gitignore | 1 + AGENTS.md | 101 +++++++++++++++ CLAUDE.md | 101 +++++++++++++++ 9 files changed, 734 insertions(+) create mode 100644 .claude/skills/gitnexus/gitnexus-cli/SKILL.md create mode 100644 .claude/skills/gitnexus/gitnexus-debugging/SKILL.md create mode 100644 .claude/skills/gitnexus/gitnexus-exploring/SKILL.md create mode 100644 .claude/skills/gitnexus/gitnexus-guide/SKILL.md create mode 100644 .claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md create mode 100644 .claude/skills/gitnexus/gitnexus-refactoring/SKILL.md create mode 100644 AGENTS.md create mode 100644 CLAUDE.md diff --git a/.claude/skills/gitnexus/gitnexus-cli/SKILL.md b/.claude/skills/gitnexus/gitnexus-cli/SKILL.md new file mode 100644 index 000000000..c9e0af341 --- /dev/null +++ b/.claude/skills/gitnexus/gitnexus-cli/SKILL.md @@ -0,0 +1,82 @@ +--- +name: gitnexus-cli +description: "Use when the user needs to run GitNexus CLI commands like analyze/index a repo, check status, clean the index, generate a wiki, or list indexed repos. Examples: \"Index this repo\", \"Reanalyze the codebase\", \"Generate a wiki\"" +--- + +# GitNexus CLI Commands + +All commands work via `npx` — no global install required. + +## Commands + +### analyze — Build or refresh the index + +```bash +npx gitnexus analyze +``` + +Run from the project root. This parses all source files, builds the knowledge graph, writes it to `.gitnexus/`, and generates CLAUDE.md / AGENTS.md context files. + +| Flag | Effect | +| -------------- | ---------------------------------------------------------------- | +| `--force` | Force full re-index even if up to date | +| `--embeddings` | Enable embedding generation for semantic search (off by default) | + +**When to run:** First time in a project, after major code changes, or when `gitnexus://repo/{name}/context` reports the index is stale. In Claude Code, a PostToolUse hook runs `analyze` automatically after `git commit` and `git merge`, preserving embeddings if previously generated. + +### status — Check index freshness + +```bash +npx gitnexus status +``` + +Shows whether the current repo has a GitNexus index, when it was last updated, and symbol/relationship counts. Use this to check if re-indexing is needed. + +### clean — Delete the index + +```bash +npx gitnexus clean +``` + +Deletes the `.gitnexus/` directory and unregisters the repo from the global registry. Use before re-indexing if the index is corrupt or after removing GitNexus from a project. + +| Flag | Effect | +| --------- | ------------------------------------------------- | +| `--force` | Skip confirmation prompt | +| `--all` | Clean all indexed repos, not just the current one | + +### wiki — Generate documentation from the graph + +```bash +npx gitnexus wiki +``` + +Generates repository documentation from the knowledge graph using an LLM. Requires an API key (saved to `~/.gitnexus/config.json` on first use). + +| Flag | Effect | +| ------------------- | ----------------------------------------- | +| `--force` | Force full regeneration | +| `--model ` | LLM model (default: minimax/minimax-m2.5) | +| `--base-url ` | LLM API base URL | +| `--api-key ` | LLM API key | +| `--concurrency ` | Parallel LLM calls (default: 3) | +| `--gist` | Publish wiki as a public GitHub Gist | + +### list — Show all indexed repos + +```bash +npx gitnexus list +``` + +Lists all repositories registered in `~/.gitnexus/registry.json`. The MCP `list_repos` tool provides the same information. + +## After Indexing + +1. **Read `gitnexus://repo/{name}/context`** to verify the index loaded +2. Use the other GitNexus skills (`exploring`, `debugging`, `impact-analysis`, `refactoring`) for your task + +## Troubleshooting + +- **"Not inside a git repository"**: Run from a directory inside a git repo +- **Index is stale after re-analyzing**: Restart Claude Code to reload the MCP server +- **Embeddings slow**: Omit `--embeddings` (it's off by default) or set `OPENAI_API_KEY` for faster API-based embedding diff --git a/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md b/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md new file mode 100644 index 000000000..9510b97ac --- /dev/null +++ b/.claude/skills/gitnexus/gitnexus-debugging/SKILL.md @@ -0,0 +1,89 @@ +--- +name: gitnexus-debugging +description: "Use when the user is debugging a bug, tracing an error, or asking why something fails. Examples: \"Why is X failing?\", \"Where does this error come from?\", \"Trace this bug\"" +--- + +# Debugging with GitNexus + +## When to Use + +- "Why is this function failing?" +- "Trace where this error comes from" +- "Who calls this method?" +- "This endpoint returns 500" +- Investigating bugs, errors, or unexpected behavior + +## Workflow + +``` +1. gitnexus_query({query: ""}) → Find related execution flows +2. gitnexus_context({name: ""}) → See callers/callees/processes +3. READ gitnexus://repo/{name}/process/{name} → Trace execution flow +4. gitnexus_cypher({query: "MATCH path..."}) → Custom traces if needed +``` + +> If "Index is stale" → run `npx gitnexus analyze` in terminal. + +## Checklist + +``` +- [ ] Understand the symptom (error message, unexpected behavior) +- [ ] gitnexus_query for error text or related code +- [ ] Identify the suspect function from returned processes +- [ ] gitnexus_context to see callers and callees +- [ ] Trace execution flow via process resource if applicable +- [ ] gitnexus_cypher for custom call chain traces if needed +- [ ] Read source files to confirm root cause +``` + +## Debugging Patterns + +| Symptom | GitNexus Approach | +| -------------------- | ---------------------------------------------------------- | +| Error message | `gitnexus_query` for error text → `context` on throw sites | +| Wrong return value | `context` on the function → trace callees for data flow | +| Intermittent failure | `context` → look for external calls, async deps | +| Performance issue | `context` → find symbols with many callers (hot paths) | +| Recent regression | `detect_changes` to see what your changes affect | + +## Tools + +**gitnexus_query** — find code related to error: + +``` +gitnexus_query({query: "payment validation error"}) +→ Processes: CheckoutFlow, ErrorHandling +→ Symbols: validatePayment, handlePaymentError, PaymentException +``` + +**gitnexus_context** — full context for a suspect: + +``` +gitnexus_context({name: "validatePayment"}) +→ Incoming calls: processCheckout, webhookHandler +→ Outgoing calls: verifyCard, fetchRates (external API!) +→ Processes: CheckoutFlow (step 3/7) +``` + +**gitnexus_cypher** — custom call chain traces: + +```cypher +MATCH path = (a)-[:CodeRelation {type: 'CALLS'}*1..2]->(b:Function {name: "validatePayment"}) +RETURN [n IN nodes(path) | n.name] AS chain +``` + +## Example: "Payment endpoint returns 500 intermittently" + +``` +1. gitnexus_query({query: "payment error handling"}) + → Processes: CheckoutFlow, ErrorHandling + → Symbols: validatePayment, handlePaymentError + +2. gitnexus_context({name: "validatePayment"}) + → Outgoing calls: verifyCard, fetchRates (external API!) + +3. READ gitnexus://repo/my-app/process/CheckoutFlow + → Step 3: validatePayment → calls fetchRates (external) + +4. Root cause: fetchRates calls external API without proper timeout +``` diff --git a/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md b/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md new file mode 100644 index 000000000..927a4e4b6 --- /dev/null +++ b/.claude/skills/gitnexus/gitnexus-exploring/SKILL.md @@ -0,0 +1,78 @@ +--- +name: gitnexus-exploring +description: "Use when the user asks how code works, wants to understand architecture, trace execution flows, or explore unfamiliar parts of the codebase. Examples: \"How does X work?\", \"What calls this function?\", \"Show me the auth flow\"" +--- + +# Exploring Codebases with GitNexus + +## When to Use + +- "How does authentication work?" +- "What's the project structure?" +- "Show me the main components" +- "Where is the database logic?" +- Understanding code you haven't seen before + +## Workflow + +``` +1. READ gitnexus://repos → Discover indexed repos +2. READ gitnexus://repo/{name}/context → Codebase overview, check staleness +3. gitnexus_query({query: ""}) → Find related execution flows +4. gitnexus_context({name: ""}) → Deep dive on specific symbol +5. READ gitnexus://repo/{name}/process/{name} → Trace full execution flow +``` + +> If step 2 says "Index is stale" → run `npx gitnexus analyze` in terminal. + +## Checklist + +``` +- [ ] READ gitnexus://repo/{name}/context +- [ ] gitnexus_query for the concept you want to understand +- [ ] Review returned processes (execution flows) +- [ ] gitnexus_context on key symbols for callers/callees +- [ ] READ process resource for full execution traces +- [ ] Read source files for implementation details +``` + +## Resources + +| Resource | What you get | +| --------------------------------------- | ------------------------------------------------------- | +| `gitnexus://repo/{name}/context` | Stats, staleness warning (~150 tokens) | +| `gitnexus://repo/{name}/clusters` | All functional areas with cohesion scores (~300 tokens) | +| `gitnexus://repo/{name}/cluster/{name}` | Area members with file paths (~500 tokens) | +| `gitnexus://repo/{name}/process/{name}` | Step-by-step execution trace (~200 tokens) | + +## Tools + +**gitnexus_query** — find execution flows related to a concept: + +``` +gitnexus_query({query: "payment processing"}) +→ Processes: CheckoutFlow, RefundFlow, WebhookHandler +→ Symbols grouped by flow with file locations +``` + +**gitnexus_context** — 360-degree view of a symbol: + +``` +gitnexus_context({name: "validateUser"}) +→ Incoming calls: loginHandler, apiMiddleware +→ Outgoing calls: checkToken, getUserById +→ Processes: LoginFlow (step 2/5), TokenRefresh (step 1/3) +``` + +## Example: "How does payment processing work?" + +``` +1. READ gitnexus://repo/my-app/context → 918 symbols, 45 processes +2. gitnexus_query({query: "payment processing"}) + → CheckoutFlow: processPayment → validateCard → chargeStripe + → RefundFlow: initiateRefund → calculateRefund → processRefund +3. gitnexus_context({name: "processPayment"}) + → Incoming: checkoutHandler, webhookHandler + → Outgoing: validateCard, chargeStripe, saveTransaction +4. Read src/payments/processor.ts for implementation details +``` diff --git a/.claude/skills/gitnexus/gitnexus-guide/SKILL.md b/.claude/skills/gitnexus/gitnexus-guide/SKILL.md new file mode 100644 index 000000000..937ac73d1 --- /dev/null +++ b/.claude/skills/gitnexus/gitnexus-guide/SKILL.md @@ -0,0 +1,64 @@ +--- +name: gitnexus-guide +description: "Use when the user asks about GitNexus itself — available tools, how to query the knowledge graph, MCP resources, graph schema, or workflow reference. Examples: \"What GitNexus tools are available?\", \"How do I use GitNexus?\"" +--- + +# GitNexus Guide + +Quick reference for all GitNexus MCP tools, resources, and the knowledge graph schema. + +## Always Start Here + +For any task involving code understanding, debugging, impact analysis, or refactoring: + +1. **Read `gitnexus://repo/{name}/context`** — codebase overview + check index freshness +2. **Match your task to a skill below** and **read that skill file** +3. **Follow the skill's workflow and checklist** + +> If step 1 warns the index is stale, run `npx gitnexus analyze` in the terminal first. + +## Skills + +| Task | Skill to read | +| -------------------------------------------- | ------------------- | +| Understand architecture / "How does X work?" | `gitnexus-exploring` | +| Blast radius / "What breaks if I change X?" | `gitnexus-impact-analysis` | +| Trace bugs / "Why is X failing?" | `gitnexus-debugging` | +| Rename / extract / split / refactor | `gitnexus-refactoring` | +| Tools, resources, schema reference | `gitnexus-guide` (this file) | +| Index, status, clean, wiki CLI commands | `gitnexus-cli` | + +## Tools Reference + +| Tool | What it gives you | +| ---------------- | ------------------------------------------------------------------------ | +| `query` | Process-grouped code intelligence — execution flows related to a concept | +| `context` | 360-degree symbol view — categorized refs, processes it participates in | +| `impact` | Symbol blast radius — what breaks at depth 1/2/3 with confidence | +| `detect_changes` | Git-diff impact — what do your current changes affect | +| `rename` | Multi-file coordinated rename with confidence-tagged edits | +| `cypher` | Raw graph queries (read `gitnexus://repo/{name}/schema` first) | +| `list_repos` | Discover indexed repos | + +## Resources Reference + +Lightweight reads (~100-500 tokens) for navigation: + +| Resource | Content | +| ---------------------------------------------- | ----------------------------------------- | +| `gitnexus://repo/{name}/context` | Stats, staleness check | +| `gitnexus://repo/{name}/clusters` | All functional areas with cohesion scores | +| `gitnexus://repo/{name}/cluster/{clusterName}` | Area members | +| `gitnexus://repo/{name}/processes` | All execution flows | +| `gitnexus://repo/{name}/process/{processName}` | Step-by-step trace | +| `gitnexus://repo/{name}/schema` | Graph schema for Cypher | + +## Graph Schema + +**Nodes:** File, Function, Class, Interface, Method, Community, Process +**Edges (via CodeRelation.type):** CALLS, IMPORTS, EXTENDS, IMPLEMENTS, DEFINES, MEMBER_OF, STEP_IN_PROCESS + +```cypher +MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "myFunc"}) +RETURN caller.name, caller.filePath +``` diff --git a/.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md b/.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md new file mode 100644 index 000000000..e19af280c --- /dev/null +++ b/.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md @@ -0,0 +1,97 @@ +--- +name: gitnexus-impact-analysis +description: "Use when the user wants to know what will break if they change something, or needs safety analysis before editing code. Examples: \"Is it safe to change X?\", \"What depends on this?\", \"What will break?\"" +--- + +# Impact Analysis with GitNexus + +## When to Use + +- "Is it safe to change this function?" +- "What will break if I modify X?" +- "Show me the blast radius" +- "Who uses this code?" +- Before making non-trivial code changes +- Before committing — to understand what your changes affect + +## Workflow + +``` +1. gitnexus_impact({target: "X", direction: "upstream"}) → What depends on this +2. READ gitnexus://repo/{name}/processes → Check affected execution flows +3. gitnexus_detect_changes() → Map current git changes to affected flows +4. Assess risk and report to user +``` + +> If "Index is stale" → run `npx gitnexus analyze` in terminal. + +## Checklist + +``` +- [ ] gitnexus_impact({target, direction: "upstream"}) to find dependents +- [ ] Review d=1 items first (these WILL BREAK) +- [ ] Check high-confidence (>0.8) dependencies +- [ ] READ processes to check affected execution flows +- [ ] gitnexus_detect_changes() for pre-commit check +- [ ] Assess risk level and report to user +``` + +## Understanding Output + +| Depth | Risk Level | Meaning | +| ----- | ---------------- | ------------------------ | +| d=1 | **WILL BREAK** | Direct callers/importers | +| d=2 | LIKELY AFFECTED | Indirect dependencies | +| d=3 | MAY NEED TESTING | Transitive effects | + +## Risk Assessment + +| Affected | Risk | +| ------------------------------ | -------- | +| <5 symbols, few processes | LOW | +| 5-15 symbols, 2-5 processes | MEDIUM | +| >15 symbols or many processes | HIGH | +| Critical path (auth, payments) | CRITICAL | + +## Tools + +**gitnexus_impact** — the primary tool for symbol blast radius: + +``` +gitnexus_impact({ + target: "validateUser", + direction: "upstream", + minConfidence: 0.8, + maxDepth: 3 +}) + +→ d=1 (WILL BREAK): + - loginHandler (src/auth/login.ts:42) [CALLS, 100%] + - apiMiddleware (src/api/middleware.ts:15) [CALLS, 100%] + +→ d=2 (LIKELY AFFECTED): + - authRouter (src/routes/auth.ts:22) [CALLS, 95%] +``` + +**gitnexus_detect_changes** — git-diff based impact analysis: + +``` +gitnexus_detect_changes({scope: "staged"}) + +→ Changed: 5 symbols in 3 files +→ Affected: LoginFlow, TokenRefresh, APIMiddlewarePipeline +→ Risk: MEDIUM +``` + +## Example: "What breaks if I change validateUser?" + +``` +1. gitnexus_impact({target: "validateUser", direction: "upstream"}) + → d=1: loginHandler, apiMiddleware (WILL BREAK) + → d=2: authRouter, sessionManager (LIKELY AFFECTED) + +2. READ gitnexus://repo/my-app/processes + → LoginFlow and TokenRefresh touch validateUser + +3. Risk: 2 direct callers, 2 processes = MEDIUM +``` diff --git a/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md b/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md new file mode 100644 index 000000000..f48cc01bd --- /dev/null +++ b/.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md @@ -0,0 +1,121 @@ +--- +name: gitnexus-refactoring +description: "Use when the user wants to rename, extract, split, move, or restructure code safely. Examples: \"Rename this function\", \"Extract this into a module\", \"Refactor this class\", \"Move this to a separate file\"" +--- + +# Refactoring with GitNexus + +## When to Use + +- "Rename this function safely" +- "Extract this into a module" +- "Split this service" +- "Move this to a new file" +- Any task involving renaming, extracting, splitting, or restructuring code + +## Workflow + +``` +1. gitnexus_impact({target: "X", direction: "upstream"}) → Map all dependents +2. gitnexus_query({query: "X"}) → Find execution flows involving X +3. gitnexus_context({name: "X"}) → See all incoming/outgoing refs +4. Plan update order: interfaces → implementations → callers → tests +``` + +> If "Index is stale" → run `npx gitnexus analyze` in terminal. + +## Checklists + +### Rename Symbol + +``` +- [ ] gitnexus_rename({symbol_name: "oldName", new_name: "newName", dry_run: true}) — preview all edits +- [ ] Review graph edits (high confidence) and ast_search edits (review carefully) +- [ ] If satisfied: gitnexus_rename({..., dry_run: false}) — apply edits +- [ ] gitnexus_detect_changes() — verify only expected files changed +- [ ] Run tests for affected processes +``` + +### Extract Module + +``` +- [ ] gitnexus_context({name: target}) — see all incoming/outgoing refs +- [ ] gitnexus_impact({target, direction: "upstream"}) — find all external callers +- [ ] Define new module interface +- [ ] Extract code, update imports +- [ ] gitnexus_detect_changes() — verify affected scope +- [ ] Run tests for affected processes +``` + +### Split Function/Service + +``` +- [ ] gitnexus_context({name: target}) — understand all callees +- [ ] Group callees by responsibility +- [ ] gitnexus_impact({target, direction: "upstream"}) — map callers to update +- [ ] Create new functions/services +- [ ] Update callers +- [ ] gitnexus_detect_changes() — verify affected scope +- [ ] Run tests for affected processes +``` + +## Tools + +**gitnexus_rename** — automated multi-file rename: + +``` +gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true}) +→ 12 edits across 8 files +→ 10 graph edits (high confidence), 2 ast_search edits (review) +→ Changes: [{file_path, edits: [{line, old_text, new_text, confidence}]}] +``` + +**gitnexus_impact** — map all dependents first: + +``` +gitnexus_impact({target: "validateUser", direction: "upstream"}) +→ d=1: loginHandler, apiMiddleware, testUtils +→ Affected Processes: LoginFlow, TokenRefresh +``` + +**gitnexus_detect_changes** — verify your changes after refactoring: + +``` +gitnexus_detect_changes({scope: "all"}) +→ Changed: 8 files, 12 symbols +→ Affected processes: LoginFlow, TokenRefresh +→ Risk: MEDIUM +``` + +**gitnexus_cypher** — custom reference queries: + +```cypher +MATCH (caller)-[:CodeRelation {type: 'CALLS'}]->(f:Function {name: "validateUser"}) +RETURN caller.name, caller.filePath ORDER BY caller.filePath +``` + +## Risk Rules + +| Risk Factor | Mitigation | +| ------------------- | ----------------------------------------- | +| Many callers (>5) | Use gitnexus_rename for automated updates | +| Cross-area refs | Use detect_changes after to verify scope | +| String/dynamic refs | gitnexus_query to find them | +| External/public API | Version and deprecate properly | + +## Example: Rename `validateUser` to `authenticateUser` + +``` +1. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: true}) + → 12 edits: 10 graph (safe), 2 ast_search (review) + → Files: validator.ts, login.ts, middleware.ts, config.json... + +2. Review ast_search edits (config.json: dynamic reference!) + +3. gitnexus_rename({symbol_name: "validateUser", new_name: "authenticateUser", dry_run: false}) + → Applied 12 edits across 8 files + +4. gitnexus_detect_changes({scope: "all"}) + → Affected: LoginFlow, TokenRefresh + → Risk: MEDIUM — run tests for these flows +``` diff --git a/.gitignore b/.gitignore index c1a6b8874..4f7f5bbad 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ packages/dbml-connector/keys # coverage coverage/ +.gitnexus diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..16ec0db11 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,101 @@ + +# GitNexus — Code Intelligence + +This project is indexed by GitNexus as **dbml** (6719 symbols, 17161 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. + +> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. + +## Always Do + +- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user. +- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows. +- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits. +- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. +- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`. + +## When Debugging + +1. `gitnexus_query({query: ""})` — find execution flows related to the issue +2. `gitnexus_context({name: ""})` — see all callers, callees, and process participation +3. `READ gitnexus://repo/dbml/process/{processName}` — trace the full execution flow step by step +4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed + +## When Refactoring + +- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`. +- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code. +- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed. + +## Never Do + +- NEVER edit a function, class, or method without first running `gitnexus_impact` on it. +- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis. +- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph. +- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope. + +## Tools Quick Reference + +| Tool | When to use | Command | +|------|-------------|---------| +| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` | +| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` | +| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` | +| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` | +| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` | +| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` | + +## Impact Risk Levels + +| Depth | Meaning | Action | +|-------|---------|--------| +| d=1 | WILL BREAK — direct callers/importers | MUST update these | +| d=2 | LIKELY AFFECTED — indirect deps | Should test | +| d=3 | MAY NEED TESTING — transitive | Test if critical path | + +## Resources + +| Resource | Use for | +|----------|---------| +| `gitnexus://repo/dbml/context` | Codebase overview, check index freshness | +| `gitnexus://repo/dbml/clusters` | All functional areas | +| `gitnexus://repo/dbml/processes` | All execution flows | +| `gitnexus://repo/dbml/process/{name}` | Step-by-step execution trace | + +## Self-Check Before Finishing + +Before completing any code modification task, verify: +1. `gitnexus_impact` was run for all modified symbols +2. No HIGH/CRITICAL risk warnings were ignored +3. `gitnexus_detect_changes()` confirms changes match expected scope +4. All d=1 (WILL BREAK) dependents were updated + +## Keeping the Index Fresh + +After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it: + +```bash +npx gitnexus analyze +``` + +If the index previously included embeddings, preserve them by adding `--embeddings`: + +```bash +npx gitnexus analyze --embeddings +``` + +To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.** + +> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`. + +## CLI + +| Task | Read this skill file | +|------|---------------------| +| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` | +| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` | +| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` | +| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` | +| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` | +| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` | + + \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..16ec0db11 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,101 @@ + +# GitNexus — Code Intelligence + +This project is indexed by GitNexus as **dbml** (6719 symbols, 17161 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. + +> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. + +## Always Do + +- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user. +- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows. +- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits. +- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance. +- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`. + +## When Debugging + +1. `gitnexus_query({query: ""})` — find execution flows related to the issue +2. `gitnexus_context({name: ""})` — see all callers, callees, and process participation +3. `READ gitnexus://repo/dbml/process/{processName}` — trace the full execution flow step by step +4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed + +## When Refactoring + +- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`. +- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code. +- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed. + +## Never Do + +- NEVER edit a function, class, or method without first running `gitnexus_impact` on it. +- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis. +- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph. +- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope. + +## Tools Quick Reference + +| Tool | When to use | Command | +|------|-------------|---------| +| `query` | Find code by concept | `gitnexus_query({query: "auth validation"})` | +| `context` | 360-degree view of one symbol | `gitnexus_context({name: "validateUser"})` | +| `impact` | Blast radius before editing | `gitnexus_impact({target: "X", direction: "upstream"})` | +| `detect_changes` | Pre-commit scope check | `gitnexus_detect_changes({scope: "staged"})` | +| `rename` | Safe multi-file rename | `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` | +| `cypher` | Custom graph queries | `gitnexus_cypher({query: "MATCH ..."})` | + +## Impact Risk Levels + +| Depth | Meaning | Action | +|-------|---------|--------| +| d=1 | WILL BREAK — direct callers/importers | MUST update these | +| d=2 | LIKELY AFFECTED — indirect deps | Should test | +| d=3 | MAY NEED TESTING — transitive | Test if critical path | + +## Resources + +| Resource | Use for | +|----------|---------| +| `gitnexus://repo/dbml/context` | Codebase overview, check index freshness | +| `gitnexus://repo/dbml/clusters` | All functional areas | +| `gitnexus://repo/dbml/processes` | All execution flows | +| `gitnexus://repo/dbml/process/{name}` | Step-by-step execution trace | + +## Self-Check Before Finishing + +Before completing any code modification task, verify: +1. `gitnexus_impact` was run for all modified symbols +2. No HIGH/CRITICAL risk warnings were ignored +3. `gitnexus_detect_changes()` confirms changes match expected scope +4. All d=1 (WILL BREAK) dependents were updated + +## Keeping the Index Fresh + +After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it: + +```bash +npx gitnexus analyze +``` + +If the index previously included embeddings, preserve them by adding `--embeddings`: + +```bash +npx gitnexus analyze --embeddings +``` + +To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.** + +> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`. + +## CLI + +| Task | Read this skill file | +|------|---------------------| +| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` | +| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` | +| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` | +| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` | +| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` | +| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` | + + \ No newline at end of file From 7f07aefbfe9969a6b99891af2054a18e0a16c4cb Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Fri, 20 Mar 2026 11:13:54 +0700 Subject: [PATCH 17/31] Update gitnexus index --- AGENTS.md | 4 ++-- CLAUDE.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 16ec0db11..dd8bbb10d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **dbml** (6719 symbols, 17161 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **dbml** (6722 symbols, 17164 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. @@ -98,4 +98,4 @@ To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats. | Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` | | Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` | - \ No newline at end of file + diff --git a/CLAUDE.md b/CLAUDE.md index 16ec0db11..dd8bbb10d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **dbml** (6719 symbols, 17161 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **dbml** (6722 symbols, 17164 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. @@ -98,4 +98,4 @@ To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats. | Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` | | Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` | - \ No newline at end of file + From d31be3895ca89ce7b379c78655d21423bcedaa32 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Wed, 25 Mar 2026 11:45:39 +0700 Subject: [PATCH 18/31] Support highlighting and suggestion for DiagramView --- packages/dbml-core/src/index.ts | 2 + .../services/suggestions/general.test.ts | 8 + .../suggestions_diagramview.test.ts | 143 ++++++++++++++++++ .../compiler/queries/container/scopeKind.ts | 2 + packages/dbml-parse/src/compiler/types.ts | 1 + packages/dbml-parse/src/index.ts | 2 + packages/dbml-parse/src/services/index.ts | 2 + packages/dbml-parse/src/services/monarch.ts | 135 +++++++++++++++++ .../src/services/suggestions/provider.ts | 125 ++++++++++++++- 9 files changed, 419 insertions(+), 1 deletion(-) create mode 100644 packages/dbml-parse/__tests__/examples/services/suggestions/suggestions_diagramview.test.ts create mode 100644 packages/dbml-parse/src/services/monarch.ts diff --git a/packages/dbml-core/src/index.ts b/packages/dbml-core/src/index.ts index 02a84710d..3bbf6ca56 100644 --- a/packages/dbml-core/src/index.ts +++ b/packages/dbml-core/src/index.ts @@ -38,6 +38,8 @@ export { formatRecordValue, // DiagramView exports syncDiagramView, + // Monaco editor syntax highlighting + dbmlMonarchTokensProvider, } from '@dbml/parse'; // Re-export types diff --git a/packages/dbml-parse/__tests__/examples/services/suggestions/general.test.ts b/packages/dbml-parse/__tests__/examples/services/suggestions/general.test.ts index a93652b65..ab077cd38 100644 --- a/packages/dbml-parse/__tests__/examples/services/suggestions/general.test.ts +++ b/packages/dbml-parse/__tests__/examples/services/suggestions/general.test.ts @@ -23,10 +23,12 @@ describe('[example] CompletionItemProvider', () => { expect(labels).toContain('Ref'); expect(labels).toContain('TablePartial'); expect(labels).toContain('Records'); + expect(labels).toContain('DiagramView'); // Test insertTexts - should have Records keyword const insertTexts = result.suggestions.map((s) => s.insertText); expect(insertTexts).toContain('Records'); + expect(insertTexts).toContain('DiagramView'); }); it('- work even if some characters have been typed out', () => { @@ -42,10 +44,12 @@ describe('[example] CompletionItemProvider', () => { const labels = result.suggestions.map((s) => s.label); expect(labels).toContain('Table'); expect(labels).toContain('Records'); + expect(labels).toContain('DiagramView'); // Test insertTexts - should have Records keyword const insertTexts = result.suggestions.map((s) => s.insertText); expect(insertTexts).toContain('Records'); + expect(insertTexts).toContain('DiagramView'); }); it('- work even if there are some not directly following nonsensical characters', () => { @@ -61,10 +65,12 @@ describe('[example] CompletionItemProvider', () => { const labels = result.suggestions.map((s) => s.label); expect(labels).toContain('Table'); expect(labels).toContain('Records'); + expect(labels).toContain('DiagramView'); // Test insertTexts - should have Records keyword const insertTexts = result.suggestions.map((s) => s.insertText); expect(insertTexts).toContain('Records'); + expect(insertTexts).toContain('DiagramView'); }); it('- work even if there are some directly following nonsensical characters', () => { @@ -80,10 +86,12 @@ describe('[example] CompletionItemProvider', () => { const labels = result.suggestions.map((s) => s.label); expect(labels).toContain('Table'); expect(labels).toContain('Records'); + expect(labels).toContain('DiagramView'); // Test insertTexts - should have Records keyword const insertTexts = result.suggestions.map((s) => s.insertText); expect(insertTexts).toContain('Records'); + expect(insertTexts).toContain('DiagramView'); }); }); diff --git a/packages/dbml-parse/__tests__/examples/services/suggestions/suggestions_diagramview.test.ts b/packages/dbml-parse/__tests__/examples/services/suggestions/suggestions_diagramview.test.ts new file mode 100644 index 000000000..a79d5c079 --- /dev/null +++ b/packages/dbml-parse/__tests__/examples/services/suggestions/suggestions_diagramview.test.ts @@ -0,0 +1,143 @@ +import { describe, expect, it } from 'vitest'; +import Compiler from '@/compiler'; +import DBMLCompletionItemProvider from '@/services/suggestions/provider'; +import { createMockTextModel, createPosition } from '@tests/utils'; + +describe('[DiagramView] CompletionItemProvider', () => { + describe('top-level suggestions should include DiagramView', () => { + it('suggests DiagramView at top level', () => { + const program = ''; + const compiler = new Compiler(); + compiler.setSource(program); + const model = createMockTextModel(program); + const provider = new DBMLCompletionItemProvider(compiler); + const position = createPosition(1, 1); + const result = provider.provideCompletionItems(model, position); + + const labels = result.suggestions.map((s) => s.label); + expect(labels).toContain('DiagramView'); + }); + }); + + describe('inside DiagramView body', () => { + it('suggests sub-block keywords (Tables, TableGroups, Notes, Schemas) and wildcard', () => { + const program = 'DiagramView my_view {\n \n}'; + const compiler = new Compiler(); + compiler.setSource(program); + const model = createMockTextModel(program); + const provider = new DBMLCompletionItemProvider(compiler); + const position = createPosition(2, 3); + const result = provider.provideCompletionItems(model, position); + + const labels = result.suggestions.map((s) => s.label); + expect(labels).toContain('Tables'); + expect(labels).toContain('TableGroups'); + expect(labels).toContain('Notes'); + expect(labels).toContain('Schemas'); + expect(labels).toContain('*'); + }); + }); + + describe('inside DiagramView Tables sub-block', () => { + it('suggests table names and wildcard', () => { + const program = [ + 'Table users {', + ' id int', + '}', + 'Table posts {', + ' id int', + '}', + 'DiagramView my_view {', + ' Tables {', + ' ', + ' }', + '}', + ].join('\n'); + const compiler = new Compiler(); + compiler.setSource(program); + const model = createMockTextModel(program); + const provider = new DBMLCompletionItemProvider(compiler); + const position = createPosition(9, 5); + const result = provider.provideCompletionItems(model, position); + + const labels = result.suggestions.map((s) => s.label); + expect(labels).toContain('users'); + expect(labels).toContain('posts'); + expect(labels).toContain('*'); + }); + }); + + describe('inside DiagramView TableGroups sub-block', () => { + it('suggests table group names and wildcard', () => { + const program = [ + 'Table users {', + ' id int', + '}', + 'TableGroup auth_tables {', + ' users', + '}', + 'DiagramView my_view {', + ' TableGroups {', + ' ', + ' }', + '}', + ].join('\n'); + const compiler = new Compiler(); + compiler.setSource(program); + const model = createMockTextModel(program); + const provider = new DBMLCompletionItemProvider(compiler); + const position = createPosition(9, 5); + const result = provider.provideCompletionItems(model, position); + + const labels = result.suggestions.map((s) => s.label); + expect(labels).toContain('auth_tables'); + expect(labels).toContain('*'); + }); + }); + + describe('inside DiagramView Schemas sub-block', () => { + it('suggests schema names and wildcard', () => { + const program = [ + 'Table public.users {', + ' id int', + '}', + 'DiagramView my_view {', + ' Schemas {', + ' ', + ' }', + '}', + ].join('\n'); + const compiler = new Compiler(); + compiler.setSource(program); + const model = createMockTextModel(program); + const provider = new DBMLCompletionItemProvider(compiler); + const position = createPosition(6, 5); + const result = provider.provideCompletionItems(model, position); + + const labels = result.suggestions.map((s) => s.label); + expect(labels).toContain('*'); + expect(labels).toContain('public'); + }); + }); + + describe('inside DiagramView Notes sub-block', () => { + it('suggests wildcard', () => { + const program = [ + 'DiagramView my_view {', + ' Notes {', + ' ', + ' }', + '}', + ].join('\n'); + const compiler = new Compiler(); + compiler.setSource(program); + const model = createMockTextModel(program); + const provider = new DBMLCompletionItemProvider(compiler); + const position = createPosition(3, 5); + const result = provider.provideCompletionItems(model, position); + + const labels = result.suggestions.map((s) => s.label); + expect(labels).toContain('*'); + }); + }); +}); diff --git a/packages/dbml-parse/src/compiler/queries/container/scopeKind.ts b/packages/dbml-parse/src/compiler/queries/container/scopeKind.ts index 9c4358873..a7bc3e753 100644 --- a/packages/dbml-parse/src/compiler/queries/container/scopeKind.ts +++ b/packages/dbml-parse/src/compiler/queries/container/scopeKind.ts @@ -30,6 +30,8 @@ export function containerScopeKind (this: Compiler, offset: number): ScopeKind { return ScopeKind.CHECKS; case 'records': return ScopeKind.RECORDS; + case 'diagramview': + return ScopeKind.DIAGRAMVIEW; default: return ScopeKind.CUSTOM; } diff --git a/packages/dbml-parse/src/compiler/types.ts b/packages/dbml-parse/src/compiler/types.ts index 24bb8bbea..eb2270654 100644 --- a/packages/dbml-parse/src/compiler/types.ts +++ b/packages/dbml-parse/src/compiler/types.ts @@ -11,4 +11,5 @@ export const enum ScopeKind { TABLEPARTIAL, CHECKS, RECORDS, + DIAGRAMVIEW, } diff --git a/packages/dbml-parse/src/index.ts b/packages/dbml-parse/src/index.ts index c7e7b182d..97c89bc04 100644 --- a/packages/dbml-parse/src/index.ts +++ b/packages/dbml-parse/src/index.ts @@ -64,4 +64,6 @@ export { export { syncDiagramView } from '@/compiler/queries/transform/syncDiagramView'; export type { DiagramViewSyncOperation } from '@/compiler/queries/transform/syncDiagramView'; +export { dbmlMonarchTokensProvider } from '@/services/monarch'; + export { Compiler, services }; diff --git a/packages/dbml-parse/src/services/index.ts b/packages/dbml-parse/src/services/index.ts index 55e7cb0cd..f9bcfcc4f 100644 --- a/packages/dbml-parse/src/services/index.ts +++ b/packages/dbml-parse/src/services/index.ts @@ -11,3 +11,5 @@ export { DBMLReferencesProvider, DBMLDiagnosticsProvider, }; + +export { dbmlMonarchTokensProvider } from './monarch'; diff --git a/packages/dbml-parse/src/services/monarch.ts b/packages/dbml-parse/src/services/monarch.ts new file mode 100644 index 000000000..130c8c906 --- /dev/null +++ b/packages/dbml-parse/src/services/monarch.ts @@ -0,0 +1,135 @@ +import type { languages } from 'monaco-editor-core'; + +const dbmlMonarchTokensProvider: languages.IMonarchLanguage = { + tokenPostfix: '.dbml', + brackets: [ + { open: '[', close: ']', token: 'delimiter.square' }, + { open: '(', close: ')', token: 'delimiter.parenthesis' }, + { open: '{', close: '}', token: 'delimiter.curly' }, + ], + + decls: [ + 'project', 'tablegroup', 'table', 'enum', 'ref', 'note', 'tablepartial', 'records', 'checks', + 'diagramview', + ], + + dataTypes: [ + 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'INT', 'INTEGER', 'BIGINT', 'FLOAT', 'DOUBLE', 'DECIMAL', 'DEC', 'BIT', 'BOOL', 'REAL', 'MONEY', 'BINARY_FLOAT', 'BINARY_DOUBLE', 'smallmoney', + 'ENUM', 'CHAR', 'BINARY', 'VARCHAR', 'VARBINARY', 'TINYBLOB', 'TINYTEXT', 'BLOB', 'TEXT', 'MEDIUMBLOB', 'MEDIUMTEXT', 'LONGBLOB', 'LONGTEXT', 'SET', 'INET6', 'UUID', 'NVARCHAR', 'NCHAR', 'NTEXT', 'IMAGE', 'VARCHAR2', 'NVARCHAR2', + 'DATE', 'TIME', 'DATETIME', 'DATETIME2', 'TIMESTAMP', 'YEAR', 'smalldatetime', 'datetimeoffset', + 'XML', 'sql_variant', 'uniqueidentifier', 'CURSOR', + 'BFILE', 'CLOB', 'NCLOB', 'RAW', + ], + + settings: [ + 'indexes', 'ref', 'note', 'headercolor', 'pk', 'null', 'increment', 'unique', 'default', 'note', 'primary', 'key', 'name', 'as', 'color', 'check', + 'tables', 'tablegroups', 'notes', 'schemas', + ], + + symbols: /[=>-]/, 'operators'], + + // Wildcard — standalone * gets its own token type for distinct styling + [/\*/, 'keyword.wildcard'], + + // Quoted column name followed by type + [/("[^"\\]*(?:\\.[^"\\]*)*")(\s+)(@idtf(?:\.@idtf*)*)/, ['string', '', 'keyword']], + + // strings + [/"([^"\\]|\\.)*$/, ''], + [/'([^'\\]|\\.)*$/, ''], + [/"/, 'string', '@string_double'], + [/'/, 'string', '@string_single'], + [/`/, 'string', '@string_backtick'], + + [/(@idtf)(\s+)(@idtf(?:\.@idtf)*)/, { + cases: { + '$1@decls': ['keyword', '', 'identifier'], + '$1==not': { + cases: { + '$3==null': ['keyword', '', 'keyword'], + '@default': ['identifier', '', 'identifier'], + }, + }, + '@default': ['identifier', '', 'keyword'], + }, + }], + [/@idtf/, { + cases: { + '@dataTypes': 'keyword', + '@decls': 'keyword', + '@settings': 'keyword', + '@default': 'identifier', + }, + }], + ], + + numbers: [ + [/0[xX][0-9a-fA-F]*/, 'number'], + [/[$][+-]*\d*(\.\d*)?/, 'number'], + [/((\d+(\.\d*)?)|(\.\d+))([eE][\-+]?\d+)?/, 'number'], + [/#([0-9A-F]{3}){1,2}/, 'number.hex'], + ], + + string_double: [ + [/[^\\"]+/, 'string'], + [/@escapes/, 'string.escape'], + [/\\./, 'string.escape.invalid'], + [/"/, 'string', '@pop'], + ], + + string_single: [ + [/[^\\']+/, 'string'], + [/@escapes/, 'string.escape'], + [/\\./, 'string.escape.invalid'], + [/'/, 'string', '@pop'], + ], + + string_backtick: [ + [/[^\\`$]+/, 'string'], + [/@escapes/, 'string.escape'], + [/`/, 'string', '@pop'], + ], + + endTripleQuotesString: [ + [/\\'/, 'string'], + [/(.*[^\\])?(\\\\)*'''/, 'string', '@popall'], + [/.*$/, 'string'], + ], + + whitespace: [ + [/[ \t\r\n]+/, ''], + [/\/\*/, 'comment', '@comment'], + [/\/\/.*$/, 'comment'], + [/'''(.*[^\\])?(\\\\)*'''/, 'string'], + [/'''.*$/, 'string', '@endTripleQuotesString'], + ], + + comment: [ + [/[^\/*]+/, 'comment'], + [/\*\//, 'comment', '@pop'], + [/[\/*]/, 'comment'], + ], + }, +}; + +export { dbmlMonarchTokensProvider }; diff --git a/packages/dbml-parse/src/services/suggestions/provider.ts b/packages/dbml-parse/src/services/suggestions/provider.ts index d59b5eede..d1896d29a 100644 --- a/packages/dbml-parse/src/services/suggestions/provider.ts +++ b/packages/dbml-parse/src/services/suggestions/provider.ts @@ -572,6 +572,20 @@ function suggestInSubField ( } case ScopeKind.TABLEGROUP: return suggestInTableGroupField(compiler); + case ScopeKind.DIAGRAMVIEW: + return suggestInDiagramViewField(); + case ScopeKind.CUSTOM: { + // Check if inside a DiagramView sub-block (Tables, Schemas, etc.) + const element = compiler.container.element(offset); + if ( + element instanceof ElementDeclarationNode + && element.parent instanceof ElementDeclarationNode + && element.parent.type?.value.toLowerCase() === 'diagramview' + ) { + return suggestInDiagramViewSubBlock(compiler, offset); + } + return noSuggestions(); + } default: return noSuggestions(); } @@ -579,7 +593,7 @@ function suggestInSubField ( function suggestTopLevelElementType (): CompletionList { return { - suggestions: ['Table', 'TableGroup', 'Enum', 'Project', 'Ref', 'TablePartial', 'Records'].map((name) => ({ + suggestions: ['Table', 'TableGroup', 'Enum', 'Project', 'Ref', 'TablePartial', 'Records', 'DiagramView'].map((name) => ({ label: name, insertText: name, insertTextRules: CompletionItemInsertTextRule.KeepWhitespace, @@ -798,6 +812,115 @@ function suggestInTableGroupField (compiler: Compiler): CompletionList { }; } +function suggestInDiagramViewField (): CompletionList { + return { + suggestions: [ + ...['Tables', 'TableGroups', 'Notes', 'Schemas'].map((name) => ({ + label: name, + insertText: name, + insertTextRules: CompletionItemInsertTextRule.KeepWhitespace, + kind: CompletionItemKind.Keyword, + range: undefined as any, + })), + { + label: '*', + insertText: '*', + insertTextRules: CompletionItemInsertTextRule.KeepWhitespace, + kind: CompletionItemKind.Keyword, + range: undefined as any, + }, + ], + }; +} + +function suggestInDiagramViewSubBlock (compiler: Compiler, offset: number): CompletionList { + const element = compiler.container.element(offset); + if (!(element instanceof ElementDeclarationNode)) return noSuggestions(); + + const blockType = (element as ElementDeclarationNode).type?.value.toLowerCase(); + const wildcard = { + label: '*', + insertText: '*', + insertTextRules: CompletionItemInsertTextRule.KeepWhitespace, + kind: CompletionItemKind.Keyword, + range: undefined as any, + }; + + switch (blockType) { + case 'tables': + return { + suggestions: [ + wildcard, + ...addQuoteToSuggestionIfNeeded({ + suggestions: [...compiler.parse.publicSymbolTable().entries()].flatMap(([index]) => { + const res = destructureIndex(index).unwrap_or(undefined); + if (res === undefined) return []; + const { kind, name } = res; + if (kind !== SymbolKind.Table && kind !== SymbolKind.Schema) return []; + return { + label: name, + insertText: name, + insertTextRules: CompletionItemInsertTextRule.KeepWhitespace, + kind: pickCompletionItemKind(kind), + range: undefined as any, + }; + }), + }).suggestions, + ], + }; + case 'tablegroups': + return { + suggestions: [ + wildcard, + ...addQuoteToSuggestionIfNeeded({ + suggestions: [...compiler.parse.publicSymbolTable().entries()].flatMap(([index]) => { + const res = destructureIndex(index).unwrap_or(undefined); + if (res === undefined) return []; + const { kind, name } = res; + if (kind !== SymbolKind.TableGroup) return []; + return { + label: name, + insertText: name, + insertTextRules: CompletionItemInsertTextRule.KeepWhitespace, + kind: pickCompletionItemKind(kind), + range: undefined as any, + }; + }), + }).suggestions, + ], + }; + case 'schemas': { + const schemaNames = new Set(); + const ast = compiler.parse.ast(); + for (const el of ast?.body || []) { + if (el instanceof ElementDeclarationNode && el.name instanceof InfixExpressionNode && el.name.op?.value === '.') { + const fragments = destructureMemberAccessExpression(el.name).unwrap_or([]); + if (fragments.length >= 2) { + const schemaName = extractVariableFromExpression(fragments[0]).unwrap_or(''); + if (schemaName) schemaNames.add(schemaName); + } + } + } + return { + suggestions: [ + wildcard, + ...[...schemaNames].map((name) => ({ + label: name, + insertText: name, + insertTextRules: CompletionItemInsertTextRule.KeepWhitespace, + kind: CompletionItemKind.Module, + range: undefined as any, + })), + ], + }; + } + case 'notes': + return { suggestions: [wildcard] }; + default: + return noSuggestions(); + } +} + function suggestInIndex (compiler: Compiler, offset: number): CompletionList { return suggestColumnNameInIndexes(compiler, offset); } From ada8ffc4c58d37bbfe10707f564764393b93b575 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Wed, 25 Mar 2026 16:47:04 +0700 Subject: [PATCH 19/31] fix: resolve table aliases to real names in DiagramView interpreter When a table has an alias (e.g., Table users as U), the DiagramView interpreter now resolves the alias to the real table name via the bound referee symbol. Previously, using an alias in Tables { U } would output { name: 'U' } which the renderer couldn't find. Now the interpreter checks field.callee's referee (set by the binder) and extracts the real name from the original declaration, falling back to the literal text for unbound references. --- .../examples/interpreter/interpreter.test.ts | 76 +++++++++++++++++++ .../elementInterpreter/diagramView.ts | 16 +++- 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts b/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts index 0a12db0f9..5aac3639a 100644 --- a/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts +++ b/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts @@ -1182,6 +1182,82 @@ describe('[example] interpreter', () => { }); }); + describe('DiagramView alias resolution', () => { + test('should resolve table alias to real name', () => { + const source = ` + Table users as U { id int } + DiagramView myView { + Tables { U } + } + `; + const db = interpret(source).getValue()!; + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tables).toEqual([{ name: 'users', schemaName: 'public' }]); + }); + + test('should resolve schema-qualified table alias', () => { + const source = ` + Table public.articles as A { id int } + DiagramView myView { + Tables { A } + } + `; + const db = interpret(source).getValue()!; + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tables).toEqual([{ name: 'articles', schemaName: 'public' }]); + }); + + test('should keep real name when no alias is used', () => { + const source = ` + Table users { id int } + DiagramView myView { + Tables { users } + } + `; + const db = interpret(source).getValue()!; + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tables).toEqual([{ name: 'users', schemaName: 'public' }]); + }); + + test('should resolve multiple aliases in same block', () => { + const source = ` + Table users as U { id int } + Table posts as P { id int } + DiagramView myView { + Tables { + U + P + } + } + `; + const db = interpret(source).getValue()!; + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tables).toEqual([ + { name: 'users', schemaName: 'public' }, + { name: 'posts', schemaName: 'public' }, + ]); + }); + + test('should resolve mixed aliases and real names', () => { + const source = ` + Table users as U { id int } + Table posts { id int } + DiagramView myView { + Tables { + U + posts + } + } + `; + const db = interpret(source).getValue()!; + const ve = db.diagramViews[0].visibleEntities; + expect(ve.tables).toEqual([ + { name: 'users', schemaName: 'public' }, + { name: 'posts', schemaName: 'public' }, + ]); + }); + }); + describe('standalone note interpretation', () => { test('should interpret standalone note', () => { const source = ` diff --git a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts index 9cfbee781..b85f26359 100644 --- a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts +++ b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts @@ -1,5 +1,5 @@ import { partition } from 'lodash-es'; -import { destructureComplexVariable } from '@/core/analyzer/utils'; +import { destructureComplexVariable, extractReferee } from '@/core/analyzer/utils'; import { CompileError } from '@/core/errors'; import { BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, PrimaryExpressionNode, SyntaxNode, VariableNode } from '@/core/parser/nodes'; import { ElementInterpreter, InterpreterDatabase, DiagramView } from '@/core/interpreter/types'; @@ -158,6 +158,20 @@ export class DiagramViewInterpreter implements ElementInterpreter { for (const field of body.body) { if (!(field instanceof FunctionApplicationNode)) continue; + // If the field was bound to a symbol (e.g., alias "U" → Table "users"), + // resolve the real name from the referee's declaration + const referee = extractReferee(field.callee); + if (referee?.declaration instanceof ElementDeclarationNode) { + const realFragments = destructureComplexVariable(referee.declaration.name).unwrap_or([]); + if (realFragments.length > 0) { + const name = realFragments[realFragments.length - 1]; + const schemaName = realFragments.length > 1 ? realFragments.slice(0, -1).join('.') : DEFAULT_SCHEMA_NAME; + items.push({ name, schemaName }); + continue; + } + } + + // Fallback: use the literal text (for unbound references or non-table blocks) const fragments = destructureComplexVariable(field.callee).unwrap_or([]); if (fragments.length === 0) continue; From f970018e34463b7c8a14f4a61dc0107d82542ba4 Mon Sep 17 00:00:00 2001 From: NQPhuc <11730168+NQPhuc@users.noreply.github.com> Date: Wed, 25 Mar 2026 17:28:19 +0700 Subject: [PATCH 20/31] v7.0.0-alpha.0 --- dbml-playground/package.json | 4 ++-- lerna.json | 2 +- packages/dbml-cli/package.json | 6 +++--- packages/dbml-connector/package.json | 2 +- packages/dbml-core/package.json | 4 ++-- packages/dbml-parse/package.json | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dbml-playground/package.json b/dbml-playground/package.json index 7c4a9d4a4..feb006a20 100644 --- a/dbml-playground/package.json +++ b/dbml-playground/package.json @@ -1,6 +1,6 @@ { "name": "@dbml/playground", - "version": "6.4.0", + "version": "7.0.0-alpha.0", "description": "Interactive playground for debugging and visualizing the DBML parser pipeline", "author": "Holistics ", "license": "Apache-2.0", @@ -25,7 +25,7 @@ "format": "prettier --write src/" }, "dependencies": { - "@dbml/parse": "^6.4.0", + "@dbml/parse": "^7.0.0-alpha.0", "lodash-es": "^4.17.21", "monaco-editor": "^0.52.2", "monaco-vim": "^0.4.2", diff --git a/lerna.json b/lerna.json index f27895bc2..70e8e5ed5 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "6.4.0", + "version": "7.0.0-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 9af0d5f8e..9cbe64b0c 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": "6.4.0", + "version": "7.0.0-alpha.0", "description": "", "main": "lib/index.js", "license": "Apache-2.0", @@ -32,8 +32,8 @@ ], "dependencies": { "@babel/cli": "^7.21.0", - "@dbml/connector": "^6.4.0", - "@dbml/core": "^6.4.0", + "@dbml/connector": "^7.0.0-alpha.0", + "@dbml/core": "^7.0.0-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 7a3ed9ba5..ca9d2b2ea 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": "6.4.0", + "version": "7.0.0-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 aa626c5b0..91c9f8712 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": "6.4.0", + "version": "7.0.0-alpha.0", "description": "> TODO: description", "author": "Holistics ", "license": "Apache-2.0", @@ -46,7 +46,7 @@ "lint:fix": "eslint --fix ." }, "dependencies": { - "@dbml/parse": "^6.4.0", + "@dbml/parse": "^7.0.0-alpha.0", "antlr4": "^4.13.1", "lodash": "^4.17.15", "lodash-es": "^4.17.15", diff --git a/packages/dbml-parse/package.json b/packages/dbml-parse/package.json index 3db6df99c..38ad487cc 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": "6.4.0", + "version": "7.0.0-alpha.0", "description": "DBML parser v2", "author": "Holistics ", "license": "Apache-2.0", From cb925c52bc255aa63688dc04db48f2ad5d7129c9 Mon Sep 17 00:00:00 2001 From: NQPhuc <11730168+NQPhuc@users.noreply.github.com> Date: Wed, 25 Mar 2026 18:44:32 +0700 Subject: [PATCH 21/31] fix: remove duplicate prepublish step --- packages/dbml-parse/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/dbml-parse/package.json b/packages/dbml-parse/package.json index 38ad487cc..1bf3b8248 100644 --- a/packages/dbml-parse/package.json +++ b/packages/dbml-parse/package.json @@ -30,7 +30,6 @@ "test": "vitest run", "test:watch": "vitest watch", "coverage": "vitest run --coverage", - "prepublish": "npm run build", "prepare": "npm run build", "lint": "eslint .", "lint:fix": "eslint --fix ." From cf3b6115a06828aee0971d2b73dd72d556a04b0e Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Fri, 27 Mar 2026 15:39:08 +0700 Subject: [PATCH 22/31] Re-index gitnexus --- AGENTS.md | 2 +- CLAUDE.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index dd8bbb10d..555668233 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **dbml** (6722 symbols, 17164 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **dbml** (6723 symbols, 17180 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. diff --git a/CLAUDE.md b/CLAUDE.md index dd8bbb10d..555668233 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **dbml** (6722 symbols, 17164 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **dbml** (6723 symbols, 17180 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. From acc9e086ed0912dfae87930e8033a361caf07e36 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Fri, 27 Mar 2026 17:21:51 +0700 Subject: [PATCH 23/31] Clean up code and fix bugs --- dbml-playground/package.json | 8 ---- .../binder/elementBinder/diagramView.ts | 14 +----- .../elementValidators/diagramView.ts | 13 +---- .../elementInterpreter/diagramView.ts | 14 +----- .../src/core/interpreter/interpreter.ts | 47 ++++--------------- packages/dbml-parse/src/core/parser/utils.ts | 9 ++++ 6 files changed, 22 insertions(+), 83 deletions(-) diff --git a/dbml-playground/package.json b/dbml-playground/package.json index e78c9c49e..feb006a20 100644 --- a/dbml-playground/package.json +++ b/dbml-playground/package.json @@ -1,10 +1,6 @@ { "name": "@dbml/playground", -<<<<<<< HEAD "version": "7.0.0-alpha.0", -======= - "version": "6.5.0", ->>>>>>> master "description": "Interactive playground for debugging and visualizing the DBML parser pipeline", "author": "Holistics ", "license": "Apache-2.0", @@ -29,11 +25,7 @@ "format": "prettier --write src/" }, "dependencies": { -<<<<<<< HEAD "@dbml/parse": "^7.0.0-alpha.0", -======= - "@dbml/parse": "^6.5.0", ->>>>>>> master "lodash-es": "^4.17.21", "monaco-editor": "^0.52.2", "monaco-vim": "^0.4.2", diff --git a/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts b/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts index 605173cae..4e44c9137 100644 --- a/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts +++ b/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts @@ -1,7 +1,8 @@ import { partition } from 'lodash-es'; import { - BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, PrimaryExpressionNode, ProgramNode, VariableNode, + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ProgramNode, } from '../../../parser/nodes'; +import { isWildcardExpression } from '../../../parser/utils'; import { ElementBinder } from '../types'; import { SyntaxToken } from '../../../lexer/tokens'; import { CompileError } from '../../../errors'; @@ -9,17 +10,6 @@ import { lookupAndBindInScope, pickBinder, scanNonListNodeForBinding } from '../ import { SymbolKind } from '../../symbol/symbolIndex'; import SymbolFactory from '../../symbol/factory'; -/** - * Check if a node is a wildcard expression (*) - */ -function isWildcardExpression (node: unknown): boolean { - if (!node) return false; - if (node instanceof PrimaryExpressionNode && node.expression instanceof VariableNode) { - return node.expression.variable?.value === '*'; - } - return false; -} - export default class DiagramViewBinder implements ElementBinder { private symbolFactory: SymbolFactory; private declarationNode: ElementDeclarationNode & { type: SyntaxToken }; diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts index 0af3ffb36..8c9aa5ade 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts @@ -14,18 +14,7 @@ import SymbolFactory from '@/core/analyzer/symbol/factory'; import { createDiagramViewFieldSymbolIndex, createDiagramViewSymbolIndex } from '@/core/analyzer/symbol/symbolIndex'; import { destructureComplexVariable, extractVarNameFromPrimaryVariable } from '@/core/analyzer/utils'; import { DiagramViewFieldSymbol, DiagramViewSymbol } from '@/core/analyzer/symbol/symbols'; -import { isExpressionAVariableNode } from '@/core/parser/utils'; - -/** - * Check if a node is a wildcard expression (*) - */ -function isWildcardExpression (node: SyntaxNode | undefined): boolean { - if (!node) return false; - if (node instanceof PrimaryExpressionNode && node.expression instanceof VariableNode) { - return node.expression.variable?.value === '*'; - } - return false; -} +import { isExpressionAVariableNode, isWildcardExpression } from '@/core/parser/utils'; export default class DiagramViewValidator implements ElementValidator { private declarationNode: ElementDeclarationNode & { type: SyntaxToken }; diff --git a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts index b85f26359..941ae6989 100644 --- a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts +++ b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts @@ -1,22 +1,12 @@ import { partition } from 'lodash-es'; import { destructureComplexVariable, extractReferee } from '@/core/analyzer/utils'; import { CompileError } from '@/core/errors'; -import { BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, PrimaryExpressionNode, SyntaxNode, VariableNode } from '@/core/parser/nodes'; +import { BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, SyntaxNode } from '@/core/parser/nodes'; +import { isWildcardExpression } from '@/core/parser/utils'; import { ElementInterpreter, InterpreterDatabase, DiagramView } from '@/core/interpreter/types'; import { getTokenPosition } from '@/core/interpreter/utils'; import { DEFAULT_SCHEMA_NAME } from '@/constants'; -/** - * Check if a node is a wildcard expression (*) - */ -function isWildcardExpression (node: SyntaxNode | undefined): boolean { - if (!node) return false; - if (node instanceof PrimaryExpressionNode && node.expression instanceof VariableNode) { - return node.expression.variable?.value === '*'; - } - return false; -} - export class DiagramViewInterpreter implements ElementInterpreter { private declarationNode: ElementDeclarationNode; private env: InterpreterDatabase; diff --git a/packages/dbml-parse/src/core/interpreter/interpreter.ts b/packages/dbml-parse/src/core/interpreter/interpreter.ts index b82e7a2c6..a4c9f1e43 100644 --- a/packages/dbml-parse/src/core/interpreter/interpreter.ts +++ b/packages/dbml-parse/src/core/interpreter/interpreter.ts @@ -31,21 +31,16 @@ function processColumnInDb (table: T): T { } /** - * Entity types whose explicit wildcard (*) must be expanded to the concrete list of names. - * These types have no "show all" sentinel in FilterConfig — consumers expect - * an explicit list. Add more types here as needed. - */ -const WILDCARD_EXPAND_ENTITIES = new Set(['tableGroups']); - -/** - * Expand explicit wildcard ([]) in DiagramView visibleEntities to the actual list of - * entities for the types listed in WILDCARD_EXPAND_ENTITIES. + * Expand explicit wildcard ([]) for tableGroups in DiagramView visibleEntities + * to the concrete list of table group names. * * Only expands when: - * 1. The user wrote `{ * }` for that entity type (tracked via _explicitWildcards) - * 2. The other Trinity dims (Tables, Schemas) are NOT explicitly set — - * i.e. the wildcard entity is the only Trinity dim declared. + * 1. The user wrote `TableGroups { * }` (tracked via _explicitWildcards) + * 2. No other Trinity dim (Tables, Schemas) is explicitly set — + * i.e. tableGroups is the only Trinity dim declared. * When other Trinity dims are also declared, [] keeps its "show all" meaning. + * + * Also cleans up internal markers (_explicitWildcards, _explicitlySet) before output. */ function expandDiagramViewWildcards (env: InterpreterDatabase): void { if (!env.diagramViews) return; @@ -56,17 +51,7 @@ function expandDiagramViewWildcards (env: InterpreterDatabase): void { const explicitlySet = view._explicitlySet; if (!wildcards || !explicitlySet) continue; - if (WILDCARD_EXPAND_ENTITIES.has('tables') && wildcards.has('tables') && ve.tables && ve.tables.length === 0) { - const otherTrinitySet = explicitlySet.has('tableGroups') || explicitlySet.has('schemas'); - if (!otherTrinitySet) { - ve.tables = Array.from(env.tables.values()).map((t) => ({ - name: t.name, - schemaName: t.schemaName || DEFAULT_SCHEMA_NAME, - })); - } - } - - if (WILDCARD_EXPAND_ENTITIES.has('tableGroups') && wildcards.has('tableGroups') && ve.tableGroups && ve.tableGroups.length === 0) { + if (wildcards.has('tableGroups') && ve.tableGroups && ve.tableGroups.length === 0) { const otherTrinitySet = explicitlySet.has('tables') || explicitlySet.has('schemas'); if (!otherTrinitySet) { ve.tableGroups = Array.from(env.tableGroups.values()).map((tg) => ({ @@ -75,22 +60,6 @@ function expandDiagramViewWildcards (env: InterpreterDatabase): void { } } - if (WILDCARD_EXPAND_ENTITIES.has('stickyNotes') && wildcards.has('stickyNotes') && ve.stickyNotes && ve.stickyNotes.length === 0) { - // stickyNotes is not part of Trinity, no other-dim check needed - ve.stickyNotes = Array.from(env.notes.values()).map((n) => ({ - name: n.name, - })); - } - - if (WILDCARD_EXPAND_ENTITIES.has('schemas') && wildcards.has('schemas') && ve.schemas && ve.schemas.length === 0) { - const otherTrinitySet = explicitlySet.has('tables') || explicitlySet.has('tableGroups'); - if (!otherTrinitySet) { - ve.schemas = [...new Set( - Array.from(env.tables.values()).map((t) => t.schemaName || DEFAULT_SCHEMA_NAME), - )].map((name) => ({ name })); - } - } - // Clean up internal markers before output delete view._explicitWildcards; delete view._explicitlySet; diff --git a/packages/dbml-parse/src/core/parser/utils.ts b/packages/dbml-parse/src/core/parser/utils.ts index 70b179874..c605a3b3f 100644 --- a/packages/dbml-parse/src/core/parser/utils.ts +++ b/packages/dbml-parse/src/core/parser/utils.ts @@ -360,6 +360,15 @@ export function isExpressionAVariableNode ( ); } +// Return true if an expression node is a wildcard (*) +export function isWildcardExpression (node: SyntaxNode | undefined): boolean { + if (!node) return false; + if (node instanceof PrimaryExpressionNode && node.expression instanceof VariableNode) { + return node.expression.variable?.value === '*'; + } + return false; +} + // Return true if an expression node is a primary expression // with an identifier-like variable node export function isExpressionAnIdentifierNode (value?: unknown): value is PrimaryExpressionNode & { From d6c5351ffd98639e4a13517494526221ce6b0cbc Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Fri, 27 Mar 2026 17:23:56 +0700 Subject: [PATCH 24/31] Update snapshot files --- .../binder/output/sticky_notes.out.json | 8 ++++- .../validator/output/sticky_notes.out.json | 32 ++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/dbml-parse/__tests__/snapshots/binder/output/sticky_notes.out.json b/packages/dbml-parse/__tests__/snapshots/binder/output/sticky_notes.out.json index b7a6b4e77..9552b3496 100644 --- a/packages/dbml-parse/__tests__/snapshots/binder/output/sticky_notes.out.json +++ b/packages/dbml-parse/__tests__/snapshots/binder/output/sticky_notes.out.json @@ -2220,7 +2220,8 @@ "end": 204 } }, - "parent": 43 + "parent": 43, + "symbol": 4 } ], "eof": { @@ -2262,6 +2263,11 @@ } }, "declaration": 30 + }, + "Note:nodeName": { + "references": [], + "id": 4, + "declaration": 42 } }, "id": 0, diff --git a/packages/dbml-parse/__tests__/snapshots/validator/output/sticky_notes.out.json b/packages/dbml-parse/__tests__/snapshots/validator/output/sticky_notes.out.json index 540c8d0be..f73bc5b96 100644 --- a/packages/dbml-parse/__tests__/snapshots/validator/output/sticky_notes.out.json +++ b/packages/dbml-parse/__tests__/snapshots/validator/output/sticky_notes.out.json @@ -1976,7 +1976,8 @@ "end": 142 } }, - "parent": 93 + "parent": 93, + "symbol": 4 }, { "id": 44, @@ -2796,7 +2797,8 @@ "end": 210 } }, - "parent": 93 + "parent": 93, + "symbol": 5 }, { "id": 58, @@ -4180,7 +4182,8 @@ "end": 361 } }, - "parent": 93 + "parent": 93, + "symbol": 6 }, { "id": 92, @@ -4930,7 +4933,8 @@ "end": 444 } }, - "parent": 93 + "parent": 93, + "symbol": 7 } ], "eof": { @@ -4972,6 +4976,26 @@ } }, "declaration": 30 + }, + "Note:note2": { + "references": [], + "id": 4, + "declaration": 37 + }, + "Note:note3": { + "references": [], + "id": 5, + "declaration": 51 + }, + "Note:schema.note4": { + "references": [], + "id": 6, + "declaration": 77 + }, + "Note:schema.note5": { + "references": [], + "id": 7, + "declaration": 92 } }, "id": 0, From a8ad119968999f4ce254dcd3cbd3feb2d27c5fb2 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Mon, 30 Mar 2026 17:29:16 +0700 Subject: [PATCH 25/31] fix: reject wildcard (*) as element name outside DiagramView blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The DiagramView feature introduced * as a wildcard syntax but inadvertently allowed * as a name for Table, Note, TableGroup, Enum, and Ref. This was a regression — * was previously rejected. - Add WildcardToken to lexer (previously * was OP for no reason) - Add WildcardNode to parser AST, handled in extractOperand - Reject WildcardNode in all validators except DiagramView - Remove dead * entry from infixBindingPowerMap - Add 20 unit tests covering lexer, parser, and validator behavior --- AGENTS.md | 2 +- CLAUDE.md | 2 +- .../__tests__/examples/lexer/lexer.test.ts | 2 +- .../examples/wildcard/wildcard.test.ts | 228 ++ .../snapshots/lexer/output/symbols.out.json | 2 +- .../parser/output/call_expression.out.json | 511 ++--- .../parser/output/expression.out.json | 1889 +++++++++-------- .../parser/output/trailing_comments.out.json | 640 +++--- .../parser/output/tuple_expression.out.json | 363 ++-- .../dbml-parse/__tests__/utils/compiler.ts | 7 + .../validator/elementValidators/checks.ts | 4 + .../validator/elementValidators/custom.ts | 5 +- .../validator/elementValidators/enum.ts | 5 +- .../validator/elementValidators/indexes.ts | 4 + .../validator/elementValidators/note.ts | 5 +- .../validator/elementValidators/project.ts | 5 +- .../validator/elementValidators/records.ts | 5 +- .../validator/elementValidators/ref.ts | 5 +- .../validator/elementValidators/table.ts | 4 + .../validator/elementValidators/tableGroup.ts | 5 +- .../elementValidators/tablePartial.ts | 4 + packages/dbml-parse/src/core/lexer/lexer.ts | 3 + packages/dbml-parse/src/core/lexer/tokens.ts | 2 +- packages/dbml-parse/src/core/parser/nodes.ts | 11 + packages/dbml-parse/src/core/parser/parser.ts | 21 +- packages/dbml-parse/src/core/parser/utils.ts | 10 +- 26 files changed, 2083 insertions(+), 1661 deletions(-) create mode 100644 packages/dbml-parse/__tests__/examples/wildcard/wildcard.test.ts diff --git a/AGENTS.md b/AGENTS.md index 555668233..7fbaf02d0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **dbml** (6723 symbols, 17180 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **dbml** (7342 symbols, 18681 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. diff --git a/CLAUDE.md b/CLAUDE.md index 555668233..7fbaf02d0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **dbml** (6723 symbols, 17180 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **dbml** (7342 symbols, 18681 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. diff --git a/packages/dbml-parse/__tests__/examples/lexer/lexer.test.ts b/packages/dbml-parse/__tests__/examples/lexer/lexer.test.ts index bacd09bf3..c5b26ed35 100644 --- a/packages/dbml-parse/__tests__/examples/lexer/lexer.test.ts +++ b/packages/dbml-parse/__tests__/examples/lexer/lexer.test.ts @@ -139,7 +139,7 @@ describe('[example] lexer', () => { expect(tokens.map((t) => ({ kind: t.kind, value: t.value }))).toEqual([ { kind: SyntaxTokenKind.OP, value: '+' }, { kind: SyntaxTokenKind.OP, value: '-' }, - { kind: SyntaxTokenKind.OP, value: '*' }, + { kind: SyntaxTokenKind.WILDCARD, value: '*' }, { kind: SyntaxTokenKind.OP, value: '/' }, { kind: SyntaxTokenKind.OP, value: '<' }, { kind: SyntaxTokenKind.OP, value: '>' }, diff --git a/packages/dbml-parse/__tests__/examples/wildcard/wildcard.test.ts b/packages/dbml-parse/__tests__/examples/wildcard/wildcard.test.ts new file mode 100644 index 000000000..ff04b4fa9 --- /dev/null +++ b/packages/dbml-parse/__tests__/examples/wildcard/wildcard.test.ts @@ -0,0 +1,228 @@ +import { describe, expect, test } from 'vitest'; +import { SyntaxTokenKind, isTriviaToken } from '@/core/lexer/tokens'; +import { CompileErrorCode } from '@/core/errors'; +import { SyntaxNodeKind } from '@/core/parser/nodes'; +import { WildcardNode } from '@/core/parser/nodes'; +import { isWildcardExpression } from '@/core/parser/utils'; +import { lex, parse, analyze } from '@tests/utils'; + +// Helper to get non-trivia, non-EOF tokens +function getTokens (source: string) { + return lex(source).getValue().filter((t) => !isTriviaToken(t) && t.kind !== SyntaxTokenKind.EOF); +} + +describe('[example] wildcard', () => { + // ── Sub-Problem 1: Lexer — WildcardToken ── + + describe('lexer', () => { + test('should lex * as WILDCARD token, not OP', () => { + const tokens = getTokens('*'); + + expect(tokens).toHaveLength(1); + expect(tokens[0].kind).toBe(SyntaxTokenKind.WILDCARD); + expect(tokens[0].value).toBe('*'); + }); + + test('should lex * as WILDCARD among other tokens', () => { + const source = 'Tables { * }'; + const tokens = getTokens(source); + const wildcardToken = tokens.find((t) => t.value === '*'); + + expect(wildcardToken).toBeDefined(); + expect(wildcardToken!.kind).toBe(SyntaxTokenKind.WILDCARD); + }); + + test('should not affect other operators', () => { + const tokens = getTokens('+ - / % < > = .'); + const kinds = tokens.map((t) => t.kind); + + expect(kinds.every((k) => k === SyntaxTokenKind.OP)).toBe(true); + }); + }); + + // ── Sub-Problem 2: Parser — WildcardNode ── + + describe('parser', () => { + test('should parse * as WildcardNode', () => { + const source = 'Table t { * }'; + const { ast } = parse(source).getValue(); + const table = ast.body[0]; + const body = table.body as any; + const field = body.body[0]; + + expect(field.callee).toBeInstanceOf(WildcardNode); + expect(field.callee.kind).toBe(SyntaxNodeKind.WILDCARD); + }); + + test('isWildcardExpression should return true for WildcardNode', () => { + const source = 'Table t { * }'; + const { ast } = parse(source).getValue(); + const table = ast.body[0]; + const body = table.body as any; + const field = body.body[0]; + + expect(isWildcardExpression(field.callee)).toBe(true); + }); + + test('isWildcardExpression should return false for non-wildcard', () => { + const source = 'Table t { id int }'; + const { ast } = parse(source).getValue(); + const table = ast.body[0]; + const body = table.body as any; + const field = body.body[0]; + + expect(isWildcardExpression(field.callee)).toBe(false); + }); + + test('isWildcardExpression should return false for undefined', () => { + expect(isWildcardExpression(undefined)).toBe(false); + }); + }); + + // ── Sub-Problem 3: Validators — Reject * outside DiagramView ── + + describe('validator: reject * as element name', () => { + test('should reject * as Table name', () => { + const source = 'Table * { id int }'; + const errors = analyze(source).getErrors(); + + expect(errors.length).toBeGreaterThan(0); + const wildcardError = errors.find((e) => e.code === CompileErrorCode.INVALID_NAME); + expect(wildcardError).toBeDefined(); + expect(wildcardError!.diagnostic).toContain('Wildcard'); + }); + + test('should reject * as Enum name', () => { + const source = 'Enum * { value1 }'; + const errors = analyze(source).getErrors(); + + const wildcardError = errors.find((e) => e.code === CompileErrorCode.INVALID_NAME); + expect(wildcardError).toBeDefined(); + expect(wildcardError!.diagnostic).toContain('Wildcard'); + }); + + test('should reject * as TableGroup name', () => { + const source = 'TableGroup * { users }'; + const errors = analyze(source).getErrors(); + + const wildcardError = errors.find((e) => e.code === CompileErrorCode.INVALID_NAME); + expect(wildcardError).toBeDefined(); + expect(wildcardError!.diagnostic).toContain('Wildcard'); + }); + + test('should reject * as Ref name', () => { + const source = 'Ref *: users.id > posts.user_id'; + const errors = analyze(source).getErrors(); + + const wildcardError = errors.find((e) => e.code === CompileErrorCode.INVALID_NAME); + expect(wildcardError).toBeDefined(); + expect(wildcardError!.diagnostic).toContain('Wildcard'); + }); + + test('should reject * as Note name', () => { + const source = "Note * { 'some note' }"; + const errors = analyze(source).getErrors(); + + const wildcardError = errors.find((e) => e.code === CompileErrorCode.INVALID_NAME); + expect(wildcardError).toBeDefined(); + expect(wildcardError!.diagnostic).toContain('Wildcard'); + }); + }); + + // ── DiagramView: Allow * ── + + describe('validator: allow * inside DiagramView', () => { + test('should allow * in DiagramView body', () => { + const source = ` + DiagramView myView { + * + } + `; + const errors = analyze(source).getErrors(); + + expect(errors).toHaveLength(0); + }); + + test('should allow * in DiagramView Tables sub-block', () => { + const source = ` + DiagramView myView { + Tables { + * + } + } + `; + const errors = analyze(source).getErrors(); + + expect(errors).toHaveLength(0); + }); + + test('should allow * in DiagramView Notes sub-block', () => { + const source = ` + DiagramView myView { + Notes { + * + } + } + `; + const errors = analyze(source).getErrors(); + + expect(errors).toHaveLength(0); + }); + + test('should allow * in DiagramView TableGroups sub-block', () => { + const source = ` + DiagramView myView { + TableGroups { + * + } + } + `; + const errors = analyze(source).getErrors(); + + expect(errors).toHaveLength(0); + }); + + test('should allow specific items alongside * in DiagramView sub-block (with warning)', () => { + const source = ` + Table users { id int } + DiagramView myView { + Tables { + * + users + } + } + `; + const report = analyze(source); + const errors = report.getErrors(); + + // No wildcard-related INVALID_NAME errors + const wildcardErrors = errors.filter((e) => e.code === CompileErrorCode.INVALID_NAME && e.diagnostic.includes('Wildcard')); + expect(wildcardErrors).toHaveLength(0); + }); + }); + + // ── Edge cases ── + + describe('edge cases', () => { + test('should still allow valid table names after wildcard fix', () => { + const source = 'Table users { id int }'; + const errors = analyze(source).getErrors(); + + expect(errors).toHaveLength(0); + }); + + test('should still allow quoted table names', () => { + const source = 'Table "my table" { id int }'; + const errors = analyze(source).getErrors(); + + expect(errors).toHaveLength(0); + }); + + test('should still allow schema-qualified table names', () => { + const source = 'Table public.users { id int }'; + const errors = analyze(source).getErrors(); + + expect(errors).toHaveLength(0); + }); + }); +}); diff --git a/packages/dbml-parse/__tests__/snapshots/lexer/output/symbols.out.json b/packages/dbml-parse/__tests__/snapshots/lexer/output/symbols.out.json index f96c9481c..24ae8dc4d 100644 --- a/packages/dbml-parse/__tests__/snapshots/lexer/output/symbols.out.json +++ b/packages/dbml-parse/__tests__/snapshots/lexer/output/symbols.out.json @@ -87,7 +87,7 @@ "end": 3 }, { - "kind": "", + "kind": "", "startPos": { "offset": 4, "line": 0, diff --git a/packages/dbml-parse/__tests__/snapshots/parser/output/call_expression.out.json b/packages/dbml-parse/__tests__/snapshots/parser/output/call_expression.out.json index bff731842..f9d5a6585 100644 --- a/packages/dbml-parse/__tests__/snapshots/parser/output/call_expression.out.json +++ b/packages/dbml-parse/__tests__/snapshots/parser/output/call_expression.out.json @@ -1,6 +1,6 @@ { "value": { - "id": 32, + "id": 31, "kind": "", "startPos": { "offset": 0, @@ -18,7 +18,7 @@ "end": 69, "body": [ { - "id": 31, + "id": 30, "kind": "", "startPos": { "offset": 0, @@ -157,7 +157,7 @@ } }, "body": { - "id": 30, + "id": 29, "kind": "", "startPos": { "offset": 20, @@ -218,7 +218,7 @@ }, "body": [ { - "id": 21, + "id": 20, "kind": "", "startPos": { "offset": 27, @@ -235,8 +235,8 @@ "start": 27, "end": 53, "callee": { - "id": 6, - "kind": "", + "id": 3, + "kind": "", "startPos": { "offset": 27, "line": 1, @@ -244,37 +244,16 @@ }, "fullStart": 23, "endPos": { - "offset": 31, + "offset": 28, "line": 1, - "column": 8 + "column": 5 }, - "fullEnd": 32, + "fullEnd": 29, "start": 27, - "end": 31, - "op": { - "kind": "", - "startPos": { - "offset": 29, - "line": 1, - "column": 6 - }, - "endPos": { - "offset": 30, - "line": 1, - "column": 7 - }, - "value": "*", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 29, - "end": 30 - }, - "leftExpression": { - "id": 3, - "kind": "", + "end": 28, + "expression": { + "id": 2, + "kind": "", "startPos": { "offset": 27, "line": 1, @@ -289,156 +268,179 @@ "fullEnd": 29, "start": 27, "end": 28, - "expression": { - "id": 2, - "kind": "", + "literal": { + "kind": "", "startPos": { "offset": 27, "line": 1, "column": 4 }, - "fullStart": 23, "endPos": { "offset": 28, "line": 1, "column": 5 }, - "fullEnd": 29, - "start": 27, - "end": 28, - "literal": { - "kind": "", - "startPos": { - "offset": 27, - "line": 1, - "column": 4 + "value": "1", + "leadingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 23, + "line": 1, + "column": 0 + }, + "endPos": { + "offset": 24, + "line": 1, + "column": 1 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 23, + "end": 24 }, - "endPos": { - "offset": 28, - "line": 1, - "column": 5 + { + "kind": "", + "startPos": { + "offset": 24, + "line": 1, + "column": 1 + }, + "endPos": { + "offset": 25, + "line": 1, + "column": 2 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 24, + "end": 25 }, - "value": "1", - "leadingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 23, - "line": 1, - "column": 0 - }, - "endPos": { - "offset": 24, - "line": 1, - "column": 1 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 23, - "end": 24 + { + "kind": "", + "startPos": { + "offset": 25, + "line": 1, + "column": 2 }, - { - "kind": "", - "startPos": { - "offset": 24, - "line": 1, - "column": 1 - }, - "endPos": { - "offset": 25, - "line": 1, - "column": 2 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 24, - "end": 25 + "endPos": { + "offset": 26, + "line": 1, + "column": 3 }, - { - "kind": "", - "startPos": { - "offset": 25, - "line": 1, - "column": 2 - }, - "endPos": { - "offset": 26, - "line": 1, - "column": 3 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 25, - "end": 26 + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 25, + "end": 26 + }, + { + "kind": "", + "startPos": { + "offset": 26, + "line": 1, + "column": 3 }, - { - "kind": "", - "startPos": { - "offset": 26, - "line": 1, - "column": 3 - }, - "endPos": { - "offset": 27, - "line": 1, - "column": 4 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 26, - "end": 27 - } - ], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 28, - "line": 1, - "column": 5 - }, - "endPos": { - "offset": 29, - "line": 1, - "column": 6 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 28, - "end": 29 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 27, - "end": 28 - } + "endPos": { + "offset": 27, + "line": 1, + "column": 4 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 26, + "end": 27 + } + ], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 28, + "line": 1, + "column": 5 + }, + "endPos": { + "offset": 29, + "line": 1, + "column": 6 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 28, + "end": 29 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 27, + "end": 28 + } + } + }, + "args": [ + { + "id": 4, + "kind": "", + "startPos": { + "offset": 29, + "line": 1, + "column": 6 + }, + "fullStart": 29, + "endPos": { + "offset": 30, + "line": 1, + "column": 7 + }, + "fullEnd": 30, + "start": 29, + "end": 30, + "token": { + "kind": "", + "startPos": { + "offset": 29, + "line": 1, + "column": 6 + }, + "endPos": { + "offset": 30, + "line": 1, + "column": 7 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 29, + "end": 30 } }, - "rightExpression": { + { "id": 5, - "kind": "", + "kind": "", "startPos": { "offset": 30, "line": 1, @@ -453,72 +455,52 @@ "fullEnd": 32, "start": 30, "end": 31, - "expression": { - "id": 4, - "kind": "", + "token": { + "kind": "", "startPos": { "offset": 30, "line": 1, "column": 7 }, - "fullStart": 30, "endPos": { "offset": 31, "line": 1, "column": 8 }, - "fullEnd": 32, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 31, + "line": 1, + "column": 8 + }, + "endPos": { + "offset": 32, + "line": 1, + "column": 9 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 31, + "end": 32 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, "start": 30, - "end": 31, - "variable": { - "kind": "", - "startPos": { - "offset": 30, - "line": 1, - "column": 7 - }, - "endPos": { - "offset": 31, - "line": 1, - "column": 8 - }, - "value": "*", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 31, - "line": 1, - "column": 8 - }, - "endPos": { - "offset": 32, - "line": 1, - "column": 9 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 31, - "end": 32 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 30, - "end": 31 - } + "end": 31 } - } - }, - "args": [ + }, { - "id": 20, + "id": 19, "kind": "", "startPos": { "offset": 32, @@ -641,7 +623,7 @@ "end": 50 }, "leftExpression": { - "id": 15, + "id": 14, "kind": "", "startPos": { "offset": 32, @@ -701,7 +683,7 @@ "end": 35 }, "leftExpression": { - "id": 8, + "id": 7, "kind": "", "startPos": { "offset": 32, @@ -718,7 +700,7 @@ "start": 32, "end": 33, "expression": { - "id": 7, + "id": 6, "kind": "", "startPos": { "offset": 32, @@ -780,7 +762,7 @@ } }, "rightExpression": { - "id": 14, + "id": 13, "kind": "", "startPos": { "offset": 36, @@ -797,7 +779,7 @@ "start": 36, "end": 43, "callee": { - "id": 12, + "id": 11, "kind": "", "startPos": { "offset": 36, @@ -814,7 +796,7 @@ "start": 36, "end": 40, "callee": { - "id": 10, + "id": 9, "kind": "", "startPos": { "offset": 36, @@ -831,7 +813,7 @@ "start": 36, "end": 37, "expression": { - "id": 9, + "id": 8, "kind": "", "startPos": { "offset": 36, @@ -893,7 +875,7 @@ } }, "argumentList": { - "id": 11, + "id": 10, "kind": "", "startPos": { "offset": 38, @@ -978,7 +960,7 @@ } }, "argumentList": { - "id": 13, + "id": 12, "kind": "", "startPos": { "offset": 41, @@ -1064,7 +1046,7 @@ } }, "rightExpression": { - "id": 19, + "id": 18, "kind": "", "startPos": { "offset": 50, @@ -1081,7 +1063,7 @@ "start": 50, "end": 53, "callee": { - "id": 17, + "id": 16, "kind": "", "startPos": { "offset": 50, @@ -1098,7 +1080,7 @@ "start": 50, "end": 51, "expression": { - "id": 16, + "id": 15, "kind": "", "startPos": { "offset": 50, @@ -1138,7 +1120,7 @@ } }, "argumentList": { - "id": 18, + "id": 17, "kind": "", "startPos": { "offset": 51, @@ -1226,7 +1208,7 @@ ] }, { - "id": 29, + "id": 28, "kind": "", "startPos": { "offset": 59, @@ -1243,7 +1225,7 @@ "start": 59, "end": 64, "callee": { - "id": 28, + "id": 27, "kind": "", "startPos": { "offset": 59, @@ -1260,7 +1242,7 @@ "start": 59, "end": 64, "callee": { - "id": 26, + "id": 25, "kind": "", "startPos": { "offset": 59, @@ -1298,7 +1280,7 @@ "end": 61 }, "leftExpression": { - "id": 23, + "id": 22, "kind": "", "startPos": { "offset": 59, @@ -1315,7 +1297,7 @@ "start": 59, "end": 60, "expression": { - "id": 22, + "id": 21, "kind": "", "startPos": { "offset": 59, @@ -1440,7 +1422,7 @@ } }, "rightExpression": { - "id": 25, + "id": 24, "kind": "", "startPos": { "offset": 61, @@ -1457,7 +1439,7 @@ "start": 61, "end": 62, "expression": { - "id": 24, + "id": 23, "kind": "", "startPos": { "offset": 61, @@ -1498,7 +1480,7 @@ } }, "argumentList": { - "id": 27, + "id": 26, "kind": "", "startPos": { "offset": 62, @@ -1653,5 +1635,52 @@ "end": 69 } }, - "errors": [] + "errors": [ + { + "code": 1007, + "diagnostic": "Expect a following space", + "nodeOrToken": { + "id": 4, + "kind": "", + "startPos": { + "offset": 29, + "line": 1, + "column": 6 + }, + "fullStart": 29, + "endPos": { + "offset": 30, + "line": 1, + "column": 7 + }, + "fullEnd": 30, + "start": 29, + "end": 30, + "token": { + "kind": "", + "startPos": { + "offset": 29, + "line": 1, + "column": 6 + }, + "endPos": { + "offset": 30, + "line": 1, + "column": 7 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 29, + "end": 30 + } + }, + "start": 29, + "end": 30, + "name": "CompileError" + } + ] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/parser/output/expression.out.json b/packages/dbml-parse/__tests__/snapshots/parser/output/expression.out.json index 4ab15e46c..0d3928d67 100644 --- a/packages/dbml-parse/__tests__/snapshots/parser/output/expression.out.json +++ b/packages/dbml-parse/__tests__/snapshots/parser/output/expression.out.json @@ -218,7 +218,7 @@ }, "body": [ { - "id": 7, + "id": 6, "kind": "", "startPos": { "offset": 23, @@ -235,8 +235,8 @@ "start": 23, "end": 26, "callee": { - "id": 6, - "kind": "", + "id": 2, + "kind": "", "startPos": { "offset": 23, "line": 1, @@ -244,177 +244,161 @@ }, "fullStart": 19, "endPos": { - "offset": 26, + "offset": 24, "line": 1, - "column": 7 + "column": 5 }, - "fullEnd": 28, + "fullEnd": 24, "start": 23, - "end": 26, - "op": { - "kind": "", + "end": 24, + "token": { + "kind": "", "startPos": { - "offset": 24, + "offset": 23, "line": 1, - "column": 5 + "column": 4 }, "endPos": { - "offset": 25, + "offset": 24, "line": 1, - "column": 6 + "column": 5 }, "value": "*", - "leadingTrivia": [], + "leadingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 19, + "line": 1, + "column": 0 + }, + "endPos": { + "offset": 20, + "line": 1, + "column": 1 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 19, + "end": 20 + }, + { + "kind": "", + "startPos": { + "offset": 20, + "line": 1, + "column": 1 + }, + "endPos": { + "offset": 21, + "line": 1, + "column": 2 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 20, + "end": 21 + }, + { + "kind": "", + "startPos": { + "offset": 21, + "line": 1, + "column": 2 + }, + "endPos": { + "offset": 22, + "line": 1, + "column": 3 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 21, + "end": 22 + }, + { + "kind": "", + "startPos": { + "offset": 22, + "line": 1, + "column": 3 + }, + "endPos": { + "offset": 23, + "line": 1, + "column": 4 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 22, + "end": 23 + } + ], "trailingTrivia": [], "leadingInvalid": [], "trailingInvalid": [], "isInvalid": false, - "start": 24, - "end": 25 - }, - "leftExpression": { + "start": 23, + "end": 24 + } + }, + "args": [ + { "id": 3, - "kind": "", + "kind": "", "startPos": { - "offset": 23, + "offset": 24, "line": 1, - "column": 4 + "column": 5 }, - "fullStart": 19, + "fullStart": 24, "endPos": { - "offset": 24, + "offset": 25, "line": 1, - "column": 5 + "column": 6 }, - "fullEnd": 24, - "start": 23, - "end": 24, - "expression": { - "id": 2, - "kind": "", + "fullEnd": 25, + "start": 24, + "end": 25, + "token": { + "kind": "", "startPos": { - "offset": 23, + "offset": 24, "line": 1, - "column": 4 + "column": 5 }, - "fullStart": 19, "endPos": { - "offset": 24, + "offset": 25, "line": 1, - "column": 5 + "column": 6 }, - "fullEnd": 24, - "start": 23, - "end": 24, - "variable": { - "kind": "", - "startPos": { - "offset": 23, - "line": 1, - "column": 4 - }, - "endPos": { - "offset": 24, - "line": 1, - "column": 5 - }, - "value": "*", - "leadingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 19, - "line": 1, - "column": 0 - }, - "endPos": { - "offset": 20, - "line": 1, - "column": 1 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 19, - "end": 20 - }, - { - "kind": "", - "startPos": { - "offset": 20, - "line": 1, - "column": 1 - }, - "endPos": { - "offset": 21, - "line": 1, - "column": 2 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 20, - "end": 21 - }, - { - "kind": "", - "startPos": { - "offset": 21, - "line": 1, - "column": 2 - }, - "endPos": { - "offset": 22, - "line": 1, - "column": 3 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 21, - "end": 22 - }, - { - "kind": "", - "startPos": { - "offset": 22, - "line": 1, - "column": 3 - }, - "endPos": { - "offset": 23, - "line": 1, - "column": 4 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 22, - "end": 23 - } - ], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 23, - "end": 24 - } + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 24, + "end": 25 } }, - "rightExpression": { + { "id": 5, "kind": "", "startPos": { @@ -493,11 +477,10 @@ } } } - }, - "args": [] + ] }, { - "id": 16, + "id": 15, "kind": "", "startPos": { "offset": 38, @@ -514,7 +497,7 @@ "start": 38, "end": 47, "callee": { - "id": 15, + "id": 11, "kind": "", "startPos": { "offset": 38, @@ -523,13 +506,13 @@ }, "fullStart": 28, "endPos": { - "offset": 47, + "offset": 43, "line": 3, - "column": 13 + "column": 9 }, - "fullEnd": 49, + "fullEnd": 44, "start": 38, - "end": 47, + "end": 43, "op": { "kind": "", "startPos": { @@ -574,7 +557,7 @@ "end": 41 }, "leftExpression": { - "id": 9, + "id": 8, "kind": "", "startPos": { "offset": 38, @@ -591,7 +574,7 @@ "start": 38, "end": 39, "expression": { - "id": 8, + "id": 7, "kind": "", "startPos": { "offset": 38, @@ -843,8 +826,8 @@ } }, "rightExpression": { - "id": 14, - "kind": "", + "id": 10, + "kind": "", "startPos": { "offset": 42, "line": 3, @@ -852,138 +835,158 @@ }, "fullStart": 42, "endPos": { - "offset": 47, + "offset": 43, "line": 3, - "column": 13 + "column": 9 }, - "fullEnd": 49, + "fullEnd": 44, "start": 42, - "end": 47, - "op": { - "kind": "", + "end": 43, + "expression": { + "id": 9, + "kind": "", "startPos": { - "offset": 44, + "offset": 42, "line": 3, - "column": 10 + "column": 8 }, + "fullStart": 42, "endPos": { - "offset": 45, + "offset": 43, "line": 3, - "column": 11 - }, - "value": "*", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 45, - "line": 3, - "column": 11 - }, - "endPos": { - "offset": 46, - "line": 3, - "column": 12 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 45, - "end": 46 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 44, - "end": 45 - }, - "leftExpression": { - "id": 11, - "kind": "", - "startPos": { - "offset": 42, - "line": 3, - "column": 8 - }, - "fullStart": 42, - "endPos": { - "offset": 43, - "line": 3, - "column": 9 + "column": 9 }, "fullEnd": 44, "start": 42, "end": 43, - "expression": { - "id": 10, - "kind": "", + "literal": { + "kind": "", "startPos": { "offset": 42, "line": 3, "column": 8 }, - "fullStart": 42, "endPos": { "offset": 43, "line": 3, "column": 9 }, - "fullEnd": 44, + "value": "2", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 43, + "line": 3, + "column": 9 + }, + "endPos": { + "offset": 44, + "line": 3, + "column": 10 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 43, + "end": 44 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, "start": 42, - "end": 43, - "literal": { - "kind": "", + "end": 43 + } + } + } + }, + "args": [ + { + "id": 12, + "kind": "", + "startPos": { + "offset": 44, + "line": 3, + "column": 10 + }, + "fullStart": 44, + "endPos": { + "offset": 45, + "line": 3, + "column": 11 + }, + "fullEnd": 46, + "start": 44, + "end": 45, + "token": { + "kind": "", + "startPos": { + "offset": 44, + "line": 3, + "column": 10 + }, + "endPos": { + "offset": 45, + "line": 3, + "column": 11 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", "startPos": { - "offset": 42, + "offset": 45, "line": 3, - "column": 8 + "column": 11 }, "endPos": { - "offset": 43, + "offset": 46, "line": 3, - "column": 9 + "column": 12 }, - "value": "2", + "value": " ", "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 43, - "line": 3, - "column": 9 - }, - "endPos": { - "offset": 44, - "line": 3, - "column": 10 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 43, - "end": 44 - } - ], + "trailingTrivia": [], "leadingInvalid": [], "trailingInvalid": [], "isInvalid": false, - "start": 42, - "end": 43 + "start": 45, + "end": 46 } - } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 44, + "end": 45 + } + }, + { + "id": 14, + "kind": "", + "startPos": { + "offset": 46, + "line": 3, + "column": 12 }, - "rightExpression": { + "fullStart": 46, + "endPos": { + "offset": 47, + "line": 3, + "column": 13 + }, + "fullEnd": 49, + "start": 46, + "end": 47, + "expression": { "id": 13, - "kind": "", + "kind": "", "startPos": { "offset": 46, "line": 3, @@ -998,74 +1001,55 @@ "fullEnd": 49, "start": 46, "end": 47, - "expression": { - "id": 12, - "kind": "", + "literal": { + "kind": "", "startPos": { "offset": 46, "line": 3, "column": 12 }, - "fullStart": 46, "endPos": { "offset": 47, "line": 3, "column": 13 }, - "fullEnd": 49, + "value": "3", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 48, + "line": 3, + "column": 14 + }, + "endPos": { + "offset": 49, + "line": 4, + "column": 0 + }, + "value": "\n", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 48, + "end": 49 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, "start": 46, - "end": 47, - "literal": { - "kind": "", - "startPos": { - "offset": 46, - "line": 3, - "column": 12 - }, - "endPos": { - "offset": 47, - "line": 3, - "column": 13 - }, - "value": "3", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 48, - "line": 3, - "column": 14 - }, - "endPos": { - "offset": 49, - "line": 4, - "column": 0 - }, - "value": "\n", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 48, - "end": 49 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 46, - "end": 47 - } + "end": 47 } } } - }, - "args": [] + ] }, { - "id": 25, + "id": 24, "kind": "", "startPos": { "offset": 53, @@ -1082,7 +1066,7 @@ "start": 53, "end": 62, "callee": { - "id": 24, + "id": 23, "kind": "", "startPos": { "offset": 53, @@ -1142,7 +1126,7 @@ "end": 60 }, "leftExpression": { - "id": 21, + "id": 20, "kind": "", "startPos": { "offset": 53, @@ -1202,7 +1186,7 @@ "end": 56 }, "leftExpression": { - "id": 18, + "id": 17, "kind": "", "startPos": { "offset": 53, @@ -1219,7 +1203,7 @@ "start": 53, "end": 54, "expression": { - "id": 17, + "id": 16, "kind": "", "startPos": { "offset": 53, @@ -1366,7 +1350,7 @@ } }, "rightExpression": { - "id": 20, + "id": 19, "kind": "", "startPos": { "offset": 57, @@ -1383,7 +1367,7 @@ "start": 57, "end": 58, "expression": { - "id": 19, + "id": 18, "kind": "", "startPos": { "offset": 57, @@ -1446,7 +1430,7 @@ } }, "rightExpression": { - "id": 23, + "id": 22, "kind": "", "startPos": { "offset": 61, @@ -1463,7 +1447,7 @@ "start": 61, "end": 62, "expression": { - "id": 22, + "id": 21, "kind": "", "startPos": { "offset": 61, @@ -1528,7 +1512,7 @@ "args": [] }, { - "id": 35, + "id": 34, "kind": "", "startPos": { "offset": 68, @@ -1545,7 +1529,7 @@ "start": 68, "end": 78, "callee": { - "id": 34, + "id": 33, "kind": "", "startPos": { "offset": 68, @@ -1605,7 +1589,7 @@ "end": 76 }, "leftExpression": { - "id": 31, + "id": 30, "kind": "", "startPos": { "offset": 68, @@ -1665,7 +1649,7 @@ "end": 71 }, "leftExpression": { - "id": 27, + "id": 26, "kind": "", "startPos": { "offset": 68, @@ -1682,7 +1666,7 @@ "start": 68, "end": 69, "expression": { - "id": 26, + "id": 25, "kind": "", "startPos": { "offset": 68, @@ -1829,7 +1813,7 @@ } }, "rightExpression": { - "id": 30, + "id": 29, "kind": "", "startPos": { "offset": 72, @@ -1867,7 +1851,7 @@ "end": 73 }, "expression": { - "id": 29, + "id": 28, "kind": "", "startPos": { "offset": 73, @@ -1884,7 +1868,7 @@ "start": 73, "end": 74, "expression": { - "id": 28, + "id": 27, "kind": "", "startPos": { "offset": 73, @@ -1948,7 +1932,7 @@ } }, "rightExpression": { - "id": 33, + "id": 32, "kind": "", "startPos": { "offset": 77, @@ -1965,7 +1949,7 @@ "start": 77, "end": 78, "expression": { - "id": 32, + "id": 31, "kind": "", "startPos": { "offset": 77, @@ -2030,7 +2014,7 @@ "args": [] }, { - "id": 45, + "id": 44, "kind": "", "startPos": { "offset": 84, @@ -2047,7 +2031,7 @@ "start": 84, "end": 95, "callee": { - "id": 44, + "id": 43, "kind": "", "startPos": { "offset": 84, @@ -2107,7 +2091,7 @@ "end": 93 }, "leftExpression": { - "id": 41, + "id": 40, "kind": "", "startPos": { "offset": 84, @@ -2231,7 +2215,7 @@ }, "elementList": [ { - "id": 40, + "id": 39, "kind": "", "startPos": { "offset": 85, @@ -2291,7 +2275,7 @@ "end": 88 }, "leftExpression": { - "id": 37, + "id": 36, "kind": "", "startPos": { "offset": 85, @@ -2308,7 +2292,7 @@ "start": 85, "end": 86, "expression": { - "id": 36, + "id": 35, "kind": "", "startPos": { "offset": 85, @@ -2370,7 +2354,7 @@ } }, "rightExpression": { - "id": 39, + "id": 38, "kind": "", "startPos": { "offset": 89, @@ -2387,7 +2371,7 @@ "start": 89, "end": 90, "expression": { - "id": 38, + "id": 37, "kind": "", "startPos": { "offset": 89, @@ -2474,7 +2458,7 @@ } }, "rightExpression": { - "id": 43, + "id": 42, "kind": "", "startPos": { "offset": 94, @@ -2491,7 +2475,7 @@ "start": 94, "end": 95, "expression": { - "id": 42, + "id": 41, "kind": "", "startPos": { "offset": 94, @@ -2556,7 +2540,7 @@ "args": [] }, { - "id": 54, + "id": 53, "kind": "", "startPos": { "offset": 101, @@ -2573,7 +2557,7 @@ "start": 101, "end": 114, "callee": { - "id": 53, + "id": 52, "kind": "", "startPos": { "offset": 101, @@ -2633,7 +2617,7 @@ "end": 110 }, "leftExpression": { - "id": 50, + "id": 49, "kind": "", "startPos": { "offset": 101, @@ -2693,7 +2677,7 @@ "end": 104 }, "leftExpression": { - "id": 47, + "id": 46, "kind": "", "startPos": { "offset": 101, @@ -2710,7 +2694,7 @@ "start": 101, "end": 102, "expression": { - "id": 46, + "id": 45, "kind": "", "startPos": { "offset": 101, @@ -2857,7 +2841,7 @@ } }, "rightExpression": { - "id": 49, + "id": 48, "kind": "", "startPos": { "offset": 105, @@ -2874,7 +2858,7 @@ "start": 105, "end": 108, "expression": { - "id": 48, + "id": 47, "kind": "", "startPos": { "offset": 105, @@ -2937,7 +2921,7 @@ } }, "rightExpression": { - "id": 52, + "id": 51, "kind": "", "startPos": { "offset": 111, @@ -2954,7 +2938,7 @@ "start": 111, "end": 114, "expression": { - "id": 51, + "id": 50, "kind": "", "startPos": { "offset": 111, @@ -3019,7 +3003,7 @@ "args": [] }, { - "id": 63, + "id": 62, "kind": "", "startPos": { "offset": 122, @@ -3036,7 +3020,7 @@ "start": 122, "end": 141, "callee": { - "id": 62, + "id": 61, "kind": "", "startPos": { "offset": 122, @@ -3096,7 +3080,7 @@ "end": 134 }, "leftExpression": { - "id": 59, + "id": 58, "kind": "", "startPos": { "offset": 122, @@ -3156,7 +3140,7 @@ "end": 125 }, "leftExpression": { - "id": 56, + "id": 55, "kind": "", "startPos": { "offset": 122, @@ -3173,7 +3157,7 @@ "start": 122, "end": 123, "expression": { - "id": 55, + "id": 54, "kind": "", "startPos": { "offset": 122, @@ -3341,7 +3325,7 @@ } }, "rightExpression": { - "id": 58, + "id": 57, "kind": "", "startPos": { "offset": 131, @@ -3358,7 +3342,7 @@ "start": 131, "end": 132, "expression": { - "id": 57, + "id": 56, "kind": "", "startPos": { "offset": 131, @@ -3506,7 +3490,7 @@ } }, "rightExpression": { - "id": 61, + "id": 60, "kind": "", "startPos": { "offset": 140, @@ -3523,7 +3507,7 @@ "start": 140, "end": 141, "expression": { - "id": 60, + "id": 59, "kind": "", "startPos": { "offset": 140, @@ -3673,7 +3657,7 @@ "args": [] }, { - "id": 72, + "id": 71, "kind": "", "startPos": { "offset": 149, @@ -3690,7 +3674,7 @@ "start": 149, "end": 168, "callee": { - "id": 71, + "id": 70, "kind": "", "startPos": { "offset": 149, @@ -3835,7 +3819,7 @@ "end": 166 }, "leftExpression": { - "id": 68, + "id": 67, "kind": "", "startPos": { "offset": 149, @@ -3980,7 +3964,7 @@ "end": 157 }, "leftExpression": { - "id": 65, + "id": 64, "kind": "", "startPos": { "offset": 149, @@ -3997,7 +3981,7 @@ "start": 149, "end": 150, "expression": { - "id": 64, + "id": 63, "kind": "", "startPos": { "offset": 149, @@ -4165,7 +4149,7 @@ } }, "rightExpression": { - "id": 67, + "id": 66, "kind": "", "startPos": { "offset": 158, @@ -4182,7 +4166,7 @@ "start": 158, "end": 159, "expression": { - "id": 66, + "id": 65, "kind": "", "startPos": { "offset": 158, @@ -4245,7 +4229,7 @@ } }, "rightExpression": { - "id": 70, + "id": 69, "kind": "", "startPos": { "offset": 167, @@ -4262,7 +4246,7 @@ "start": 167, "end": 168, "expression": { - "id": 69, + "id": 68, "kind": "", "startPos": { "offset": 167, @@ -4327,7 +4311,7 @@ "args": [] }, { - "id": 81, + "id": 80, "kind": "", "startPos": { "offset": 176, @@ -4344,7 +4328,7 @@ "start": 176, "end": 181, "callee": { - "id": 80, + "id": 79, "kind": "", "startPos": { "offset": 176, @@ -4382,7 +4366,7 @@ "end": 180 }, "leftExpression": { - "id": 77, + "id": 76, "kind": "", "startPos": { "offset": 176, @@ -4420,7 +4404,7 @@ "end": 178 }, "leftExpression": { - "id": 74, + "id": 73, "kind": "", "startPos": { "offset": 176, @@ -4437,7 +4421,7 @@ "start": 176, "end": 177, "expression": { - "id": 73, + "id": 72, "kind": "", "startPos": { "offset": 176, @@ -4583,7 +4567,7 @@ } }, "rightExpression": { - "id": 76, + "id": 75, "kind": "", "startPos": { "offset": 178, @@ -4600,7 +4584,7 @@ "start": 178, "end": 179, "expression": { - "id": 75, + "id": 74, "kind": "", "startPos": { "offset": 178, @@ -4641,7 +4625,7 @@ } }, "rightExpression": { - "id": 79, + "id": 78, "kind": "", "startPos": { "offset": 180, @@ -4658,7 +4642,7 @@ "start": 180, "end": 181, "expression": { - "id": 78, + "id": 77, "kind": "", "startPos": { "offset": 180, @@ -4723,7 +4707,7 @@ "args": [] }, { - "id": 90, + "id": 89, "kind": "", "startPos": { "offset": 189, @@ -4740,7 +4724,7 @@ "start": 189, "end": 206, "callee": { - "id": 89, + "id": 88, "kind": "", "startPos": { "offset": 189, @@ -4800,7 +4784,7 @@ "end": 199 }, "leftExpression": { - "id": 86, + "id": 85, "kind": "", "startPos": { "offset": 189, @@ -4860,7 +4844,7 @@ "end": 191 }, "leftExpression": { - "id": 83, + "id": 82, "kind": "", "startPos": { "offset": 189, @@ -4877,7 +4861,7 @@ "start": 189, "end": 190, "expression": { - "id": 82, + "id": 81, "kind": "", "startPos": { "offset": 189, @@ -5023,7 +5007,7 @@ } }, "rightExpression": { - "id": 85, + "id": 84, "kind": "", "startPos": { "offset": 197, @@ -5040,7 +5024,7 @@ "start": 197, "end": 198, "expression": { - "id": 84, + "id": 83, "kind": "", "startPos": { "offset": 197, @@ -5166,7 +5150,7 @@ } }, "rightExpression": { - "id": 88, + "id": 87, "kind": "", "startPos": { "offset": 205, @@ -5183,7 +5167,7 @@ "start": 205, "end": 206, "expression": { - "id": 87, + "id": 86, "kind": "", "startPos": { "offset": 205, @@ -5333,7 +5317,7 @@ "args": [] }, { - "id": 99, + "id": 98, "kind": "", "startPos": { "offset": 214, @@ -5350,7 +5334,7 @@ "start": 214, "end": 226, "callee": { - "id": 98, + "id": 97, "kind": "", "startPos": { "offset": 214, @@ -5494,7 +5478,7 @@ "end": 225 }, "leftExpression": { - "id": 95, + "id": 94, "kind": "", "startPos": { "offset": 214, @@ -5532,7 +5516,7 @@ "end": 216 }, "leftExpression": { - "id": 92, + "id": 91, "kind": "", "startPos": { "offset": 214, @@ -5549,7 +5533,7 @@ "start": 214, "end": 215, "expression": { - "id": 91, + "id": 90, "kind": "", "startPos": { "offset": 214, @@ -5695,7 +5679,7 @@ } }, "rightExpression": { - "id": 94, + "id": 93, "kind": "", "startPos": { "offset": 216, @@ -5712,7 +5696,7 @@ "start": 216, "end": 217, "expression": { - "id": 93, + "id": 92, "kind": "", "startPos": { "offset": 216, @@ -5775,7 +5759,7 @@ } }, "rightExpression": { - "id": 97, + "id": 96, "kind": "", "startPos": { "offset": 225, @@ -5792,7 +5776,7 @@ "start": 225, "end": 226, "expression": { - "id": 96, + "id": 95, "kind": "", "startPos": { "offset": 225, @@ -5857,7 +5841,7 @@ "args": [] }, { - "id": 104, + "id": 103, "kind": "", "startPos": { "offset": 234, @@ -5874,7 +5858,7 @@ "start": 234, "end": 237, "callee": { - "id": 103, + "id": 102, "kind": "", "startPos": { "offset": 234, @@ -5891,7 +5875,7 @@ "start": 234, "end": 237, "callee": { - "id": 101, + "id": 100, "kind": "", "startPos": { "offset": 234, @@ -5908,7 +5892,7 @@ "start": 234, "end": 235, "expression": { - "id": 100, + "id": 99, "kind": "", "startPos": { "offset": 234, @@ -6054,7 +6038,7 @@ } }, "argumentList": { - "id": 102, + "id": 101, "kind": "", "startPos": { "offset": 235, @@ -6141,7 +6125,7 @@ "args": [] }, { - "id": 112, + "id": 111, "kind": "", "startPos": { "offset": 245, @@ -6158,7 +6142,7 @@ "start": 245, "end": 254, "callee": { - "id": 111, + "id": 110, "kind": "", "startPos": { "offset": 245, @@ -6303,7 +6287,7 @@ }, "elementList": [ { - "id": 106, + "id": 105, "kind": "", "startPos": { "offset": 246, @@ -6320,7 +6304,7 @@ "start": 246, "end": 247, "expression": { - "id": 105, + "id": 104, "kind": "", "startPos": { "offset": 246, @@ -6360,7 +6344,7 @@ } }, { - "id": 108, + "id": 107, "kind": "", "startPos": { "offset": 249, @@ -6377,7 +6361,7 @@ "start": 249, "end": 250, "expression": { - "id": 107, + "id": 106, "kind": "", "startPos": { "offset": 249, @@ -6417,7 +6401,7 @@ } }, { - "id": 110, + "id": 109, "kind": "", "startPos": { "offset": 252, @@ -6434,7 +6418,7 @@ "start": 252, "end": 253, "expression": { - "id": 109, + "id": 108, "kind": "", "startPos": { "offset": 252, @@ -6609,7 +6593,7 @@ "args": [] }, { - "id": 120, + "id": 119, "kind": "", "startPos": { "offset": 260, @@ -6626,7 +6610,7 @@ "start": 260, "end": 269, "callee": { - "id": 119, + "id": 118, "kind": "", "startPos": { "offset": 260, @@ -6750,7 +6734,7 @@ }, "elementList": [ { - "id": 114, + "id": 113, "kind": "", "startPos": { "offset": 261, @@ -6767,7 +6751,7 @@ "start": 261, "end": 262, "expression": { - "id": 113, + "id": 112, "kind": "", "startPos": { "offset": 261, @@ -6807,7 +6791,7 @@ } }, { - "id": 116, + "id": 115, "kind": "", "startPos": { "offset": 264, @@ -6824,7 +6808,7 @@ "start": 264, "end": 265, "expression": { - "id": 115, + "id": 114, "kind": "", "startPos": { "offset": 264, @@ -6864,7 +6848,7 @@ } }, { - "id": 118, + "id": 117, "kind": "", "startPos": { "offset": 267, @@ -6881,7 +6865,7 @@ "start": 267, "end": 268, "expression": { - "id": 117, + "id": 116, "kind": "", "startPos": { "offset": 267, @@ -7056,7 +7040,7 @@ "args": [] }, { - "id": 132, + "id": 131, "kind": "", "startPos": { "offset": 277, @@ -7073,7 +7057,7 @@ "start": 277, "end": 296, "callee": { - "id": 131, + "id": 130, "kind": "", "startPos": { "offset": 277, @@ -7218,7 +7202,7 @@ }, "elementList": [ { - "id": 130, + "id": 129, "kind": "", "startPos": { "offset": 278, @@ -7235,7 +7219,7 @@ "start": 278, "end": 295, "callee": { - "id": 122, + "id": 121, "kind": "", "startPos": { "offset": 278, @@ -7252,7 +7236,7 @@ "start": 278, "end": 279, "expression": { - "id": 121, + "id": 120, "kind": "", "startPos": { "offset": 278, @@ -7314,7 +7298,7 @@ } }, "argumentList": { - "id": 129, + "id": 128, "kind": "", "startPos": { "offset": 286, @@ -7459,7 +7443,7 @@ }, "elementList": [ { - "id": 124, + "id": 123, "kind": "", "startPos": { "offset": 287, @@ -7476,7 +7460,7 @@ "start": 287, "end": 288, "expression": { - "id": 123, + "id": 122, "kind": "", "startPos": { "offset": 287, @@ -7516,7 +7500,7 @@ } }, { - "id": 126, + "id": 125, "kind": "", "startPos": { "offset": 290, @@ -7533,7 +7517,7 @@ "start": 290, "end": 291, "expression": { - "id": 125, + "id": 124, "kind": "", "startPos": { "offset": 290, @@ -7573,7 +7557,7 @@ } }, { - "id": 128, + "id": 127, "kind": "", "startPos": { "offset": 293, @@ -7590,7 +7574,7 @@ "start": 293, "end": 294, "expression": { - "id": 127, + "id": 126, "kind": "", "startPos": { "offset": 293, @@ -7790,7 +7774,7 @@ "args": [] }, { - "id": 138, + "id": 137, "kind": "", "startPos": { "offset": 304, @@ -7807,7 +7791,7 @@ "start": 304, "end": 316, "callee": { - "id": 137, + "id": 136, "kind": "", "startPos": { "offset": 304, @@ -7952,7 +7936,7 @@ }, "elementList": [ { - "id": 134, + "id": 133, "kind": "", "startPos": { "offset": 305, @@ -7969,7 +7953,7 @@ "start": 305, "end": 306, "expression": { - "id": 133, + "id": 132, "kind": "", "startPos": { "offset": 305, @@ -8009,7 +7993,7 @@ } }, { - "id": 136, + "id": 135, "kind": "", "startPos": { "offset": 314, @@ -8026,7 +8010,7 @@ "start": 314, "end": 315, "expression": { - "id": 135, + "id": 134, "kind": "", "startPos": { "offset": 314, @@ -8281,8 +8265,8 @@ "start": 324, "end": 348, "callee": { - "id": 156, - "kind": "", + "id": 139, + "kind": "", "startPos": { "offset": 324, "line": 37, @@ -8290,59 +8274,16 @@ }, "fullStart": 319, "endPos": { - "offset": 348, + "offset": 325, "line": 37, - "column": 28 + "column": 5 }, - "fullEnd": 350, + "fullEnd": 326, "start": 324, - "end": 348, - "op": { - "kind": "", - "startPos": { - "offset": 334, - "line": 37, - "column": 14 - }, - "endPos": { - "offset": 336, - "line": 37, - "column": 16 - }, - "value": "!=", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 336, - "line": 37, - "column": 16 - }, - "endPos": { - "offset": 337, - "line": 37, - "column": 17 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 336, - "end": 337 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 334, - "end": 336 - }, - "leftExpression": { - "id": 146, - "kind": "", + "end": 325, + "expression": { + "id": 138, + "kind": "", "startPos": { "offset": 324, "line": 37, @@ -8350,39 +8291,122 @@ }, "fullStart": 319, "endPos": { - "offset": 333, + "offset": 325, "line": 37, - "column": 13 + "column": 5 }, - "fullEnd": 334, + "fullEnd": 326, "start": 324, - "end": 333, - "op": { - "kind": "", + "end": 325, + "literal": { + "kind": "", "startPos": { - "offset": 330, + "offset": 324, "line": 37, - "column": 10 + "column": 4 }, "endPos": { - "offset": 331, + "offset": 325, "line": 37, - "column": 11 + "column": 5 }, - "value": "/", - "leadingTrivia": [], - "trailingTrivia": [ + "value": "1", + "leadingTrivia": [ { - "kind": "", + "kind": "", "startPos": { - "offset": 331, - "line": 37, - "column": 11 + "offset": 319, + "line": 36, + "column": 1 }, "endPos": { - "offset": 332, + "offset": 320, "line": 37, - "column": 12 + "column": 0 + }, + "value": "\n", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 319, + "end": 320 + }, + { + "kind": "", + "startPos": { + "offset": 320, + "line": 37, + "column": 0 + }, + "endPos": { + "offset": 321, + "line": 37, + "column": 1 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 320, + "end": 321 + }, + { + "kind": "", + "startPos": { + "offset": 321, + "line": 37, + "column": 1 + }, + "endPos": { + "offset": 322, + "line": 37, + "column": 2 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 321, + "end": 322 + }, + { + "kind": "", + "startPos": { + "offset": 322, + "line": 37, + "column": 2 + }, + "endPos": { + "offset": 323, + "line": 37, + "column": 3 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 322, + "end": 323 + }, + { + "kind": "", + "startPos": { + "offset": 323, + "line": 37, + "column": 3 + }, + "endPos": { + "offset": 324, + "line": 37, + "column": 4 }, "value": " ", "leadingTrivia": [], @@ -8390,59 +8414,206 @@ "leadingInvalid": [], "trailingInvalid": [], "isInvalid": false, - "start": 331, - "end": 332 + "start": 323, + "end": 324 + } + ], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 325, + "line": 37, + "column": 5 + }, + "endPos": { + "offset": 326, + "line": 37, + "column": 6 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 325, + "end": 326 } ], "leadingInvalid": [], "trailingInvalid": [], "isInvalid": false, - "start": 330, - "end": 331 + "start": 324, + "end": 325 + } + } + }, + "args": [ + { + "id": 140, + "kind": "", + "startPos": { + "offset": 326, + "line": 37, + "column": 6 + }, + "fullStart": 326, + "endPos": { + "offset": 327, + "line": 37, + "column": 7 + }, + "fullEnd": 328, + "start": 326, + "end": 327, + "token": { + "kind": "", + "startPos": { + "offset": 326, + "line": 37, + "column": 6 + }, + "endPos": { + "offset": 327, + "line": 37, + "column": 7 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 327, + "line": 37, + "column": 7 + }, + "endPos": { + "offset": 328, + "line": 37, + "column": 8 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 327, + "end": 328 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 326, + "end": 327 + } + }, + { + "id": 148, + "kind": "", + "startPos": { + "offset": 328, + "line": 37, + "column": 8 + }, + "fullStart": 328, + "endPos": { + "offset": 338, + "line": 37, + "column": 18 + }, + "fullEnd": 339, + "start": 328, + "end": 338, + "op": { + "kind": "", + "startPos": { + "offset": 334, + "line": 37, + "column": 14 + }, + "endPos": { + "offset": 336, + "line": 37, + "column": 16 + }, + "value": "!=", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 336, + "line": 37, + "column": 16 + }, + "endPos": { + "offset": 337, + "line": 37, + "column": 17 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 336, + "end": 337 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 334, + "end": 336 }, "leftExpression": { - "id": 143, + "id": 145, "kind": "", "startPos": { - "offset": 324, + "offset": 328, "line": 37, - "column": 4 + "column": 8 }, - "fullStart": 319, + "fullStart": 328, "endPos": { - "offset": 329, + "offset": 333, "line": 37, - "column": 9 + "column": 13 }, - "fullEnd": 330, - "start": 324, - "end": 329, + "fullEnd": 334, + "start": 328, + "end": 333, "op": { "kind": "", "startPos": { - "offset": 326, + "offset": 330, "line": 37, - "column": 6 + "column": 10 }, "endPos": { - "offset": 327, + "offset": 331, "line": 37, - "column": 7 + "column": 11 }, - "value": "*", + "value": "/", "leadingTrivia": [], "trailingTrivia": [ { "kind": "", "startPos": { - "offset": 327, + "offset": 331, "line": 37, - "column": 7 + "column": 11 }, "endPos": { - "offset": 328, + "offset": 332, "line": 37, - "column": 8 + "column": 12 }, "value": " ", "leadingTrivia": [], @@ -8450,202 +8621,17 @@ "leadingInvalid": [], "trailingInvalid": [], "isInvalid": false, - "start": 327, - "end": 328 + "start": 331, + "end": 332 } ], "leadingInvalid": [], "trailingInvalid": [], "isInvalid": false, - "start": 326, - "end": 327 + "start": 330, + "end": 331 }, "leftExpression": { - "id": 140, - "kind": "", - "startPos": { - "offset": 324, - "line": 37, - "column": 4 - }, - "fullStart": 319, - "endPos": { - "offset": 325, - "line": 37, - "column": 5 - }, - "fullEnd": 326, - "start": 324, - "end": 325, - "expression": { - "id": 139, - "kind": "", - "startPos": { - "offset": 324, - "line": 37, - "column": 4 - }, - "fullStart": 319, - "endPos": { - "offset": 325, - "line": 37, - "column": 5 - }, - "fullEnd": 326, - "start": 324, - "end": 325, - "literal": { - "kind": "", - "startPos": { - "offset": 324, - "line": 37, - "column": 4 - }, - "endPos": { - "offset": 325, - "line": 37, - "column": 5 - }, - "value": "1", - "leadingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 319, - "line": 36, - "column": 1 - }, - "endPos": { - "offset": 320, - "line": 37, - "column": 0 - }, - "value": "\n", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 319, - "end": 320 - }, - { - "kind": "", - "startPos": { - "offset": 320, - "line": 37, - "column": 0 - }, - "endPos": { - "offset": 321, - "line": 37, - "column": 1 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 320, - "end": 321 - }, - { - "kind": "", - "startPos": { - "offset": 321, - "line": 37, - "column": 1 - }, - "endPos": { - "offset": 322, - "line": 37, - "column": 2 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 321, - "end": 322 - }, - { - "kind": "", - "startPos": { - "offset": 322, - "line": 37, - "column": 2 - }, - "endPos": { - "offset": 323, - "line": 37, - "column": 3 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 322, - "end": 323 - }, - { - "kind": "", - "startPos": { - "offset": 323, - "line": 37, - "column": 3 - }, - "endPos": { - "offset": 324, - "line": 37, - "column": 4 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 323, - "end": 324 - } - ], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 325, - "line": 37, - "column": 5 - }, - "endPos": { - "offset": 326, - "line": 37, - "column": 6 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 325, - "end": 326 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 324, - "end": 325 - } - } - }, - "rightExpression": { "id": 142, "kind": "", "startPos": { @@ -8723,150 +8709,89 @@ "end": 329 } } - } - }, - "rightExpression": { - "id": 145, - "kind": "", - "startPos": { - "offset": 332, - "line": 37, - "column": 12 - }, - "fullStart": 332, - "endPos": { - "offset": 333, - "line": 37, - "column": 13 - }, - "fullEnd": 334, - "start": 332, - "end": 333, - "expression": { - "id": 144, - "kind": "", - "startPos": { - "offset": 332, - "line": 37, - "column": 12 - }, - "fullStart": 332, - "endPos": { - "offset": 333, - "line": 37, - "column": 13 - }, - "fullEnd": 334, - "start": 332, - "end": 333, - "literal": { - "kind": "", - "startPos": { - "offset": 332, - "line": 37, - "column": 12 - }, - "endPos": { - "offset": 333, - "line": 37, - "column": 13 - }, - "value": "3", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 333, - "line": 37, - "column": 13 - }, - "endPos": { - "offset": 334, - "line": 37, - "column": 14 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 333, - "end": 334 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 332, - "end": 333 - } - } - } - }, - "rightExpression": { - "id": 155, - "kind": "", - "startPos": { - "offset": 337, - "line": 37, - "column": 17 - }, - "fullStart": 337, - "endPos": { - "offset": 348, - "line": 37, - "column": 28 - }, - "fullEnd": 350, - "start": 337, - "end": 348, - "op": { - "kind": "", - "startPos": { - "offset": 339, - "line": 37, - "column": 19 - }, - "endPos": { - "offset": 340, - "line": 37, - "column": 20 }, - "value": "*", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", + "rightExpression": { + "id": 144, + "kind": "", + "startPos": { + "offset": 332, + "line": 37, + "column": 12 + }, + "fullStart": 332, + "endPos": { + "offset": 333, + "line": 37, + "column": 13 + }, + "fullEnd": 334, + "start": 332, + "end": 333, + "expression": { + "id": 143, + "kind": "", "startPos": { - "offset": 340, + "offset": 332, "line": 37, - "column": 20 + "column": 12 }, + "fullStart": 332, "endPos": { - "offset": 341, + "offset": 333, "line": 37, - "column": 21 + "column": 13 }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 340, - "end": 341 + "fullEnd": 334, + "start": 332, + "end": 333, + "literal": { + "kind": "", + "startPos": { + "offset": 332, + "line": 37, + "column": 12 + }, + "endPos": { + "offset": 333, + "line": 37, + "column": 13 + }, + "value": "3", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 333, + "line": 37, + "column": 13 + }, + "endPos": { + "offset": 334, + "line": 37, + "column": 14 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 333, + "end": 334 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 332, + "end": 333 + } } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 339, - "end": 340 + } }, - "leftExpression": { - "id": 148, + "rightExpression": { + "id": 147, "kind": "", "startPos": { "offset": 337, @@ -8883,7 +8808,7 @@ "start": 337, "end": 338, "expression": { - "id": 147, + "id": 146, "kind": "", "startPos": { "offset": 337, @@ -8943,9 +8868,88 @@ "end": 338 } } + } + }, + { + "id": 156, + "kind": "", + "startPos": { + "offset": 339, + "line": 37, + "column": 19 }, - "rightExpression": { - "id": 154, + "fullStart": 339, + "endPos": { + "offset": 348, + "line": 37, + "column": 28 + }, + "fullEnd": 350, + "start": 339, + "end": 348, + "callee": { + "id": 149, + "kind": "", + "startPos": { + "offset": 339, + "line": 37, + "column": 19 + }, + "fullStart": 339, + "endPos": { + "offset": 340, + "line": 37, + "column": 20 + }, + "fullEnd": 341, + "start": 339, + "end": 340, + "token": { + "kind": "", + "startPos": { + "offset": 339, + "line": 37, + "column": 19 + }, + "endPos": { + "offset": 340, + "line": 37, + "column": 20 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 340, + "line": 37, + "column": 20 + }, + "endPos": { + "offset": 341, + "line": 37, + "column": 21 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 340, + "end": 341 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 339, + "end": 340 + } + }, + "argumentList": { + "id": 155, "kind": "", "startPos": { "offset": 341, @@ -8984,7 +8988,7 @@ }, "elementList": [ { - "id": 153, + "id": 154, "kind": "", "startPos": { "offset": 342, @@ -9044,7 +9048,7 @@ "end": 345 }, "leftExpression": { - "id": 150, + "id": 151, "kind": "", "startPos": { "offset": 342, @@ -9061,7 +9065,7 @@ "start": 342, "end": 343, "expression": { - "id": 149, + "id": 150, "kind": "", "startPos": { "offset": 342, @@ -9123,7 +9127,7 @@ } }, "rightExpression": { - "id": 152, + "id": 153, "kind": "", "startPos": { "offset": 346, @@ -9140,7 +9144,7 @@ "start": 346, "end": 347, "expression": { - "id": 151, + "id": 152, "kind": "", "startPos": { "offset": 346, @@ -9227,8 +9231,7 @@ } } } - }, - "args": [] + ] }, { "id": 163, @@ -12427,5 +12430,183 @@ "end": 463 } }, - "errors": [] + "errors": [ + { + "code": 1007, + "diagnostic": "Expect a following space", + "nodeOrToken": { + "id": 2, + "kind": "", + "startPos": { + "offset": 23, + "line": 1, + "column": 4 + }, + "fullStart": 19, + "endPos": { + "offset": 24, + "line": 1, + "column": 5 + }, + "fullEnd": 24, + "start": 23, + "end": 24, + "token": { + "kind": "", + "startPos": { + "offset": 23, + "line": 1, + "column": 4 + }, + "endPos": { + "offset": 24, + "line": 1, + "column": 5 + }, + "value": "*", + "leadingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 19, + "line": 1, + "column": 0 + }, + "endPos": { + "offset": 20, + "line": 1, + "column": 1 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 19, + "end": 20 + }, + { + "kind": "", + "startPos": { + "offset": 20, + "line": 1, + "column": 1 + }, + "endPos": { + "offset": 21, + "line": 1, + "column": 2 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 20, + "end": 21 + }, + { + "kind": "", + "startPos": { + "offset": 21, + "line": 1, + "column": 2 + }, + "endPos": { + "offset": 22, + "line": 1, + "column": 3 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 21, + "end": 22 + }, + { + "kind": "", + "startPos": { + "offset": 22, + "line": 1, + "column": 3 + }, + "endPos": { + "offset": 23, + "line": 1, + "column": 4 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 22, + "end": 23 + } + ], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 23, + "end": 24 + } + }, + "start": 23, + "end": 24, + "name": "CompileError" + }, + { + "code": 1007, + "diagnostic": "Expect a following space", + "nodeOrToken": { + "id": 3, + "kind": "", + "startPos": { + "offset": 24, + "line": 1, + "column": 5 + }, + "fullStart": 24, + "endPos": { + "offset": 25, + "line": 1, + "column": 6 + }, + "fullEnd": 25, + "start": 24, + "end": 25, + "token": { + "kind": "", + "startPos": { + "offset": 24, + "line": 1, + "column": 5 + }, + "endPos": { + "offset": 25, + "line": 1, + "column": 6 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 24, + "end": 25 + } + }, + "start": 24, + "end": 25, + "name": "CompileError" + } + ] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/parser/output/trailing_comments.out.json b/packages/dbml-parse/__tests__/snapshots/parser/output/trailing_comments.out.json index d03ccbbf4..635238bd1 100644 --- a/packages/dbml-parse/__tests__/snapshots/parser/output/trailing_comments.out.json +++ b/packages/dbml-parse/__tests__/snapshots/parser/output/trailing_comments.out.json @@ -1,6 +1,6 @@ { "value": { - "id": 94, + "id": 85, "kind": "", "startPos": { "offset": 0, @@ -18,7 +18,7 @@ "end": 396, "body": [ { - "id": 93, + "id": 84, "kind": "", "startPos": { "offset": 0, @@ -157,7 +157,7 @@ } }, "body": { - "id": 92, + "id": 83, "kind": "", "startPos": { "offset": 15, @@ -1102,7 +1102,7 @@ ] }, { - "id": 90, + "id": 81, "kind": "", "startPos": { "offset": 100, @@ -1226,7 +1226,7 @@ "end": 107 }, "body": { - "id": 89, + "id": 80, "kind": "", "startPos": { "offset": 108, @@ -3765,7 +3765,7 @@ ] }, { - "id": 71, + "id": 68, "kind": "", "startPos": { "offset": 320, @@ -3782,7 +3782,7 @@ "start": 320, "end": 326, "callee": { - "id": 70, + "id": 67, "kind": "", "startPos": { "offset": 320, @@ -3948,8 +3948,8 @@ }, "elementList": [ { - "id": 69, - "kind": "", + "id": 66, + "kind": "", "startPos": { "offset": 321, "line": 12, @@ -3957,37 +3957,16 @@ }, "fullStart": 321, "endPos": { - "offset": 325, + "offset": 323, "line": 12, - "column": 11 + "column": 9 }, - "fullEnd": 325, + "fullEnd": 323, "start": 321, - "end": 325, - "op": { - "kind": "", - "startPos": { - "offset": 323, - "line": 12, - "column": 9 - }, - "endPos": { - "offset": 324, - "line": 12, - "column": 10 - }, - "value": "*", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 323, - "end": 324 - }, - "leftExpression": { - "id": 66, - "kind": "", + "end": 323, + "expression": { + "id": 65, + "kind": "", "startPos": { "offset": 321, "line": 12, @@ -4002,101 +3981,69 @@ "fullEnd": 323, "start": 321, "end": 323, - "expression": { - "id": 65, - "kind": "", + "variable": { + "kind": "", "startPos": { "offset": 321, "line": 12, "column": 7 }, - "fullStart": 321, "endPos": { "offset": 323, "line": 12, "column": 9 }, - "fullEnd": 323, - "start": 321, - "end": 323, - "variable": { - "kind": "", - "startPos": { - "offset": 321, - "line": 12, - "column": 7 - }, - "endPos": { - "offset": 323, - "line": 12, - "column": 9 - }, - "value": "id", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 321, - "end": 323 - } - } - }, - "rightExpression": { - "id": 68, - "kind": "", - "startPos": { - "offset": 324, - "line": 12, - "column": 10 - }, - "fullStart": 324, - "endPos": { - "offset": 325, - "line": 12, - "column": 11 - }, - "fullEnd": 325, - "start": 324, - "end": 325, - "expression": { - "id": 67, - "kind": "", - "startPos": { - "offset": 324, - "line": 12, - "column": 10 - }, - "fullStart": 324, - "endPos": { - "offset": 325, - "line": 12, - "column": 11 - }, - "fullEnd": 325, - "start": 324, - "end": 325, - "literal": { - "kind": "", - "startPos": { - "offset": 324, - "line": 12, - "column": 10 - }, - "endPos": { - "offset": 325, - "line": 12, - "column": 11 + "value": "id", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [ + { + "kind": "", + "startPos": { + "offset": 323, + "line": 12, + "column": 9 + }, + "endPos": { + "offset": 324, + "line": 12, + "column": 10 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": true, + "start": 323, + "end": 324 }, - "value": "2", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 324, - "end": 325 - } + { + "kind": "", + "startPos": { + "offset": 324, + "line": 12, + "column": 10 + }, + "endPos": { + "offset": 325, + "line": 12, + "column": 11 + }, + "value": "2", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": true, + "start": 324, + "end": 325 + } + ], + "isInvalid": false, + "start": 321, + "end": 323 } } } @@ -4149,7 +4096,7 @@ "args": [] }, { - "id": 79, + "id": 73, "kind": "", "startPos": { "offset": 334, @@ -4166,7 +4113,7 @@ "start": 334, "end": 352, "callee": { - "id": 78, + "id": 72, "kind": "", "startPos": { "offset": 334, @@ -4332,8 +4279,8 @@ }, "elementList": [ { - "id": 76, - "kind": "", + "id": 70, + "kind": "", "startPos": { "offset": 335, "line": 13, @@ -4341,37 +4288,16 @@ }, "fullStart": 335, "endPos": { - "offset": 339, + "offset": 337, "line": 13, - "column": 11 + "column": 9 }, - "fullEnd": 339, + "fullEnd": 337, "start": 335, - "end": 339, - "op": { - "kind": "", - "startPos": { - "offset": 337, - "line": 13, - "column": 9 - }, - "endPos": { - "offset": 338, - "line": 13, - "column": 10 - }, - "value": "*", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 337, - "end": 338 - }, - "leftExpression": { - "id": 73, - "kind": "", + "end": 337, + "expression": { + "id": 69, + "kind": "", "startPos": { "offset": 335, "line": 13, @@ -4386,106 +4312,74 @@ "fullEnd": 337, "start": 335, "end": 337, - "expression": { - "id": 72, - "kind": "", + "variable": { + "kind": "", "startPos": { "offset": 335, "line": 13, "column": 7 }, - "fullStart": 335, "endPos": { "offset": 337, "line": 13, "column": 9 }, - "fullEnd": 337, - "start": 335, - "end": 337, - "variable": { - "kind": "", - "startPos": { - "offset": 335, - "line": 13, - "column": 7 - }, - "endPos": { - "offset": 337, - "line": 13, - "column": 9 - }, - "value": "id", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 335, - "end": 337 - } - } - }, - "rightExpression": { - "id": 75, - "kind": "", - "startPos": { - "offset": 338, - "line": 13, - "column": 10 - }, - "fullStart": 338, - "endPos": { - "offset": 339, - "line": 13, - "column": 11 - }, - "fullEnd": 339, - "start": 338, - "end": 339, - "expression": { - "id": 74, - "kind": "", - "startPos": { - "offset": 338, - "line": 13, - "column": 10 - }, - "fullStart": 338, - "endPos": { - "offset": 339, - "line": 13, - "column": 11 - }, - "fullEnd": 339, - "start": 338, - "end": 339, - "literal": { - "kind": "", - "startPos": { - "offset": 338, - "line": 13, - "column": 10 - }, - "endPos": { - "offset": 339, - "line": 13, - "column": 11 + "value": "id", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [ + { + "kind": "", + "startPos": { + "offset": 337, + "line": 13, + "column": 9 + }, + "endPos": { + "offset": 338, + "line": 13, + "column": 10 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": true, + "start": 337, + "end": 338 }, - "value": "3", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 338, - "end": 339 - } + { + "kind": "", + "startPos": { + "offset": 338, + "line": 13, + "column": 10 + }, + "endPos": { + "offset": 339, + "line": 13, + "column": 11 + }, + "value": "3", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": true, + "start": 338, + "end": 339 + } + ], + "isInvalid": false, + "start": 335, + "end": 337 } } }, { - "id": 77, + "id": 71, "kind": "", "startPos": { "offset": 340, @@ -4594,7 +4488,7 @@ "args": [] }, { - "id": 88, + "id": 79, "kind": "", "startPos": { "offset": 360, @@ -4611,7 +4505,7 @@ "start": 360, "end": 369, "callee": { - "id": 87, + "id": 78, "kind": "", "startPos": { "offset": 360, @@ -4777,8 +4671,8 @@ }, "elementList": [ { - "id": 84, - "kind": "", + "id": 75, + "kind": "", "startPos": { "offset": 361, "line": 14, @@ -4786,37 +4680,16 @@ }, "fullStart": 361, "endPos": { - "offset": 365, + "offset": 363, "line": 14, - "column": 11 + "column": 9 }, - "fullEnd": 365, + "fullEnd": 363, "start": 361, - "end": 365, - "op": { - "kind": "", - "startPos": { - "offset": 363, - "line": 14, - "column": 9 - }, - "endPos": { - "offset": 364, - "line": 14, - "column": 10 - }, - "value": "*", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 363, - "end": 364 - }, - "leftExpression": { - "id": 81, - "kind": "", + "end": 363, + "expression": { + "id": 74, + "kind": "", "startPos": { "offset": 361, "line": 14, @@ -4831,106 +4704,74 @@ "fullEnd": 363, "start": 361, "end": 363, - "expression": { - "id": 80, - "kind": "", + "variable": { + "kind": "", "startPos": { "offset": 361, "line": 14, "column": 7 }, - "fullStart": 361, "endPos": { "offset": 363, "line": 14, "column": 9 }, - "fullEnd": 363, - "start": 361, - "end": 363, - "variable": { - "kind": "", - "startPos": { - "offset": 361, - "line": 14, - "column": 7 - }, - "endPos": { - "offset": 363, - "line": 14, - "column": 9 - }, - "value": "id", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 361, - "end": 363 - } - } - }, - "rightExpression": { - "id": 83, - "kind": "", - "startPos": { - "offset": 364, - "line": 14, - "column": 10 - }, - "fullStart": 364, - "endPos": { - "offset": 365, - "line": 14, - "column": 11 - }, - "fullEnd": 365, - "start": 364, - "end": 365, - "expression": { - "id": 82, - "kind": "", - "startPos": { - "offset": 364, - "line": 14, - "column": 10 - }, - "fullStart": 364, - "endPos": { - "offset": 365, - "line": 14, - "column": 11 - }, - "fullEnd": 365, - "start": 364, - "end": 365, - "literal": { - "kind": "", - "startPos": { - "offset": 364, - "line": 14, - "column": 10 - }, - "endPos": { - "offset": 365, - "line": 14, - "column": 11 + "value": "id", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [ + { + "kind": "", + "startPos": { + "offset": 363, + "line": 14, + "column": 9 + }, + "endPos": { + "offset": 364, + "line": 14, + "column": 10 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": true, + "start": 363, + "end": 364 }, - "value": "3", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 364, - "end": 365 - } + { + "kind": "", + "startPos": { + "offset": 364, + "line": 14, + "column": 10 + }, + "endPos": { + "offset": 365, + "line": 14, + "column": 11 + }, + "value": "3", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": true, + "start": 364, + "end": 365 + } + ], + "isInvalid": false, + "start": 361, + "end": 363 } } }, { - "id": 86, + "id": 77, "kind": "", "startPos": { "offset": 366, @@ -4947,7 +4788,7 @@ "start": 366, "end": 368, "expression": { - "id": 85, + "id": 76, "kind": "", "startPos": { "offset": 366, @@ -5236,5 +5077,90 @@ "end": 396 } }, - "errors": [] + "errors": [ + { + "code": 1005, + "diagnostic": "Expect a comma ','", + "nodeOrToken": { + "kind": "", + "startPos": { + "offset": 323, + "line": 12, + "column": 9 + }, + "endPos": { + "offset": 324, + "line": 12, + "column": 10 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": true, + "start": 323, + "end": 324 + }, + "start": 323, + "end": 324, + "name": "CompileError" + }, + { + "code": 1005, + "diagnostic": "Expect a comma ','", + "nodeOrToken": { + "kind": "", + "startPos": { + "offset": 337, + "line": 13, + "column": 9 + }, + "endPos": { + "offset": 338, + "line": 13, + "column": 10 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": true, + "start": 337, + "end": 338 + }, + "start": 337, + "end": 338, + "name": "CompileError" + }, + { + "code": 1005, + "diagnostic": "Expect a comma ','", + "nodeOrToken": { + "kind": "", + "startPos": { + "offset": 363, + "line": 14, + "column": 9 + }, + "endPos": { + "offset": 364, + "line": 14, + "column": 10 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": true, + "start": 363, + "end": 364 + }, + "start": 363, + "end": 364, + "name": "CompileError" + } + ] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/snapshots/parser/output/tuple_expression.out.json b/packages/dbml-parse/__tests__/snapshots/parser/output/tuple_expression.out.json index 7cd477ed2..3b79af5ad 100644 --- a/packages/dbml-parse/__tests__/snapshots/parser/output/tuple_expression.out.json +++ b/packages/dbml-parse/__tests__/snapshots/parser/output/tuple_expression.out.json @@ -1,6 +1,6 @@ { "value": { - "id": 50, + "id": 47, "kind": "", "startPos": { "offset": 0, @@ -18,7 +18,7 @@ "end": 142, "body": [ { - "id": 49, + "id": 46, "kind": "", "startPos": { "offset": 0, @@ -157,7 +157,7 @@ } }, "body": { - "id": 48, + "id": 45, "kind": "", "startPos": { "offset": 21, @@ -1420,7 +1420,7 @@ "args": [] }, { - "id": 47, + "id": 44, "kind": "", "startPos": { "offset": 83, @@ -1437,7 +1437,7 @@ "start": 83, "end": 139, "callee": { - "id": 46, + "id": 43, "kind": "", "startPos": { "offset": 83, @@ -1779,8 +1779,8 @@ } }, { - "id": 25, - "kind": "", + "id": 22, + "kind": "", "startPos": { "offset": 91, "line": 10, @@ -1788,59 +1788,16 @@ }, "fullStart": 91, "endPos": { - "offset": 96, + "offset": 92, "line": 10, - "column": 17 + "column": 13 }, - "fullEnd": 96, + "fullEnd": 93, "start": 91, - "end": 96, - "op": { - "kind": "", - "startPos": { - "offset": 93, - "line": 10, - "column": 14 - }, - "endPos": { - "offset": 94, - "line": 10, - "column": 15 - }, - "value": "*", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 94, - "line": 10, - "column": 15 - }, - "endPos": { - "offset": 95, - "line": 10, - "column": 16 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 94, - "end": 95 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 93, - "end": 94 - }, - "leftExpression": { - "id": 22, - "kind": "", + "end": 92, + "expression": { + "id": 21, + "kind": "", "startPos": { "offset": 91, "line": 10, @@ -1855,128 +1812,118 @@ "fullEnd": 93, "start": 91, "end": 92, - "expression": { - "id": 21, - "kind": "", + "literal": { + "kind": "", "startPos": { "offset": 91, "line": 10, "column": 12 }, - "fullStart": 91, "endPos": { "offset": 92, "line": 10, "column": 13 }, - "fullEnd": 93, - "start": 91, - "end": 92, - "literal": { - "kind": "", - "startPos": { - "offset": 91, - "line": 10, - "column": 12 - }, - "endPos": { - "offset": 92, - "line": 10, - "column": 13 - }, - "value": "3", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 92, - "line": 10, - "column": 13 - }, - "endPos": { - "offset": 93, - "line": 10, - "column": 14 - }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 92, - "end": 93 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 91, - "end": 92 - } - } - }, - "rightExpression": { - "id": 24, - "kind": "", - "startPos": { - "offset": 95, - "line": 10, - "column": 16 - }, - "fullStart": 95, - "endPos": { - "offset": 96, - "line": 10, - "column": 17 - }, - "fullEnd": 96, - "start": 95, - "end": 96, - "expression": { - "id": 23, - "kind": "", - "startPos": { - "offset": 95, - "line": 10, - "column": 16 - }, - "fullStart": 95, - "endPos": { - "offset": 96, - "line": 10, - "column": 17 - }, - "fullEnd": 96, - "start": 95, - "end": 96, - "literal": { - "kind": "", - "startPos": { - "offset": 95, - "line": 10, - "column": 16 - }, - "endPos": { - "offset": 96, - "line": 10, - "column": 17 + "value": "3", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 92, + "line": 10, + "column": 13 + }, + "endPos": { + "offset": 93, + "line": 10, + "column": 14 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 92, + "end": 93 + } + ], + "leadingInvalid": [], + "trailingInvalid": [ + { + "kind": "", + "startPos": { + "offset": 93, + "line": 10, + "column": 14 + }, + "endPos": { + "offset": 94, + "line": 10, + "column": 15 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 94, + "line": 10, + "column": 15 + }, + "endPos": { + "offset": 95, + "line": 10, + "column": 16 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 94, + "end": 95 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": true, + "start": 93, + "end": 94 }, - "value": "4", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 95, - "end": 96 - } + { + "kind": "", + "startPos": { + "offset": 95, + "line": 10, + "column": 16 + }, + "endPos": { + "offset": 96, + "line": 10, + "column": 17 + }, + "value": "4", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": true, + "start": 95, + "end": 96 + } + ], + "isInvalid": false, + "start": 91, + "end": 92 } } }, { - "id": 30, + "id": 27, "kind": "", "startPos": { "offset": 98, @@ -2036,7 +1983,7 @@ "end": 101 }, "leftExpression": { - "id": 27, + "id": 24, "kind": "", "startPos": { "offset": 98, @@ -2053,7 +2000,7 @@ "start": 98, "end": 99, "expression": { - "id": 26, + "id": 23, "kind": "", "startPos": { "offset": 98, @@ -2115,7 +2062,7 @@ } }, "rightExpression": { - "id": 29, + "id": 26, "kind": "", "startPos": { "offset": 102, @@ -2132,7 +2079,7 @@ "start": 102, "end": 103, "expression": { - "id": 28, + "id": 25, "kind": "", "startPos": { "offset": 102, @@ -2173,7 +2120,7 @@ } }, { - "id": 35, + "id": 32, "kind": "", "startPos": { "offset": 105, @@ -2233,7 +2180,7 @@ "end": 109 }, "leftExpression": { - "id": 32, + "id": 29, "kind": "", "startPos": { "offset": 105, @@ -2250,7 +2197,7 @@ "start": 105, "end": 106, "expression": { - "id": 31, + "id": 28, "kind": "", "startPos": { "offset": 105, @@ -2312,7 +2259,7 @@ } }, "rightExpression": { - "id": 34, + "id": 31, "kind": "", "startPos": { "offset": 110, @@ -2329,7 +2276,7 @@ "start": 110, "end": 111, "expression": { - "id": 33, + "id": 30, "kind": "", "startPos": { "offset": 110, @@ -2370,7 +2317,7 @@ } }, { - "id": 40, + "id": 37, "kind": "", "startPos": { "offset": 113, @@ -2430,7 +2377,7 @@ "end": 117 }, "leftExpression": { - "id": 37, + "id": 34, "kind": "", "startPos": { "offset": 113, @@ -2447,7 +2394,7 @@ "start": 113, "end": 114, "expression": { - "id": 36, + "id": 33, "kind": "", "startPos": { "offset": 113, @@ -2509,7 +2456,7 @@ } }, "rightExpression": { - "id": 39, + "id": 36, "kind": "", "startPos": { "offset": 123, @@ -2526,7 +2473,7 @@ "start": 123, "end": 124, "expression": { - "id": 38, + "id": 35, "kind": "", "startPos": { "offset": 123, @@ -2652,7 +2599,7 @@ } }, { - "id": 45, + "id": 42, "kind": "", "startPos": { "offset": 126, @@ -2712,7 +2659,7 @@ "end": 130 }, "leftExpression": { - "id": 42, + "id": 39, "kind": "", "startPos": { "offset": 126, @@ -2729,7 +2676,7 @@ "start": 126, "end": 127, "expression": { - "id": 41, + "id": 38, "kind": "", "startPos": { "offset": 126, @@ -2791,7 +2738,7 @@ } }, "rightExpression": { - "id": 44, + "id": 41, "kind": "", "startPos": { "offset": 136, @@ -2808,7 +2755,7 @@ "start": 136, "end": 138, "expression": { - "id": 43, + "id": 40, "kind": "", "startPos": { "offset": 136, @@ -3294,6 +3241,56 @@ "start": 74, "end": 75, "name": "CompileError" + }, + { + "code": 1005, + "diagnostic": "Expect a comma ','", + "nodeOrToken": { + "kind": "", + "startPos": { + "offset": 93, + "line": 10, + "column": 14 + }, + "endPos": { + "offset": 94, + "line": 10, + "column": 15 + }, + "value": "*", + "leadingTrivia": [], + "trailingTrivia": [ + { + "kind": "", + "startPos": { + "offset": 94, + "line": 10, + "column": 15 + }, + "endPos": { + "offset": 95, + "line": 10, + "column": 16 + }, + "value": " ", + "leadingTrivia": [], + "trailingTrivia": [], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": false, + "start": 94, + "end": 95 + } + ], + "leadingInvalid": [], + "trailingInvalid": [], + "isInvalid": true, + "start": 93, + "end": 94 + }, + "start": 93, + "end": 94, + "name": "CompileError" } ] } \ No newline at end of file diff --git a/packages/dbml-parse/__tests__/utils/compiler.ts b/packages/dbml-parse/__tests__/utils/compiler.ts index 8f576747e..35e34780e 100644 --- a/packages/dbml-parse/__tests__/utils/compiler.ts +++ b/packages/dbml-parse/__tests__/utils/compiler.ts @@ -23,6 +23,7 @@ import { VariableNode, PrimaryExpressionNode, ArrayNode, + WildcardNode, } from '@/core/parser/nodes'; import { NodeSymbolIdGenerator } from '@/core/analyzer/symbol/symbols'; import Report from '@/core/report'; @@ -213,6 +214,12 @@ export function print (source: string, ast: SyntaxNode): string { break; } + case SyntaxNodeKind.WILDCARD: { + const wildcard = node as WildcardNode; + if (wildcard.token) collectTokens(wildcard.token); + break; + } + case SyntaxNodeKind.EMPTY: // Empty nodes don't contribute to output break; diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/checks.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/checks.ts index d865c345a..221722078 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/checks.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/checks.ts @@ -9,6 +9,7 @@ import { ListExpressionNode, ProgramNode, SyntaxNode, + WildcardNode, } from '@/core/parser/nodes'; import { isExpressionAQuotedString } from '@/core/parser/utils'; import { aggregateSettingList, pickValidator } from '@/core/analyzer/validator/utils'; @@ -54,6 +55,9 @@ export default class ChecksValidator implements ElementValidator { } private validateName (nameNode?: SyntaxNode): CompileError[] { + if (nameNode instanceof WildcardNode) { + return [new CompileError(CompileErrorCode.INVALID_NAME, 'Wildcard (*) is not allowed as a Checks name', nameNode)]; + } if (nameNode) { return [new CompileError(CompileErrorCode.UNEXPECTED_NAME, 'A Checks shouldn\'t have a name', nameNode)]; } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/custom.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/custom.ts index 1471655d7..c6f0ae740 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/custom.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/custom.ts @@ -1,6 +1,6 @@ import { CompileError, CompileErrorCode } from '@/core/errors'; import { - BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, ProgramNode, SyntaxNode, + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, ProgramNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; import SymbolFactory from '@/core/analyzer/symbol/factory'; import { SyntaxToken } from '@/core/lexer/tokens'; @@ -39,6 +39,9 @@ export default class CustomValidator implements ElementValidator { } private validateName (nameNode?: SyntaxNode): CompileError[] { + if (nameNode instanceof WildcardNode) { + return [new CompileError(CompileErrorCode.INVALID_NAME, 'Wildcard (*) is not allowed as a Custom element name', nameNode)]; + } if (nameNode) { return [new CompileError(CompileErrorCode.UNEXPECTED_NAME, 'A Custom element shouldn\'t have a name', nameNode)]; } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/enum.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/enum.ts index a4f9f74fe..5a01ec412 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/enum.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/enum.ts @@ -3,7 +3,7 @@ import { last, partition } from 'lodash-es'; import SymbolFactory from '@/core/analyzer/symbol/factory'; import { CompileError, CompileErrorCode } from '@/core/errors'; import { - BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, SyntaxNode, + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; import { isExpressionAQuotedString, isExpressionAVariableNode } from '@/core/parser/utils'; import { SyntaxToken } from '@/core/lexer/tokens'; @@ -51,6 +51,9 @@ export default class EnumValidator implements ElementValidator { if (!nameNode) { return [new CompileError(CompileErrorCode.NAME_NOT_FOUND, 'An Enum must have a name', this.declarationNode)]; } + if (nameNode instanceof WildcardNode) { + return [new CompileError(CompileErrorCode.INVALID_NAME, 'Wildcard (*) is not allowed as an Enum name', nameNode)]; + } if (!isValidName(nameNode)) { return [new CompileError(CompileErrorCode.INVALID_NAME, 'An Enum name must be of the form or .', nameNode)]; } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/indexes.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/indexes.ts index e5e313c60..14ee619f1 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/indexes.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/indexes.ts @@ -11,6 +11,7 @@ import { ProgramNode, SyntaxNode, VariableNode, + WildcardNode, } from '@/core/parser/nodes'; import { isExpressionAQuotedString, isExpressionAVariableNode } from '@/core/parser/utils'; import { aggregateSettingList } from '@/core/analyzer/validator/utils'; @@ -57,6 +58,9 @@ export default class IndexesValidator implements ElementValidator { } private validateName (nameNode?: SyntaxNode): CompileError[] { + if (nameNode instanceof WildcardNode) { + return [new CompileError(CompileErrorCode.INVALID_NAME, 'Wildcard (*) is not allowed as an Indexes name', nameNode)]; + } if (nameNode) { return [new CompileError(CompileErrorCode.UNEXPECTED_NAME, 'An Indexes shouldn\'t have a name', nameNode)]; } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/note.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/note.ts index a4993ce81..c72080ad0 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/note.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/note.ts @@ -2,7 +2,7 @@ import { partition } from 'lodash-es'; import SymbolFactory from '@/core/analyzer/symbol/factory'; import { CompileError, CompileErrorCode } from '@/core/errors'; import { - BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, ProgramNode, SyntaxNode, + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, ProgramNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; import { SyntaxToken } from '@/core/lexer/tokens'; import { ElementValidator } from '@/core/analyzer/validator/types'; @@ -69,6 +69,9 @@ export default class NoteValidator implements ElementValidator { if (!nameNode) { return [new CompileError(CompileErrorCode.INVALID_NAME, 'Sticky note must have a name', this.declarationNode)]; } + if (nameNode instanceof WildcardNode) { + return [new CompileError(CompileErrorCode.INVALID_NAME, 'Wildcard (*) is not allowed as a Note name', nameNode)]; + } const nameFragments = destructureComplexVariable(nameNode); if (!nameFragments.isOk()) return [new CompileError(CompileErrorCode.INVALID_NAME, 'Invalid name for sticky note ', this.declarationNode)]; diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/project.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/project.ts index a6fae1f79..da54246ee 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/project.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/project.ts @@ -2,7 +2,7 @@ import { partition } from 'lodash-es'; import SymbolFactory from '@/core/analyzer/symbol/factory'; import { CompileError, CompileErrorCode } from '@/core/errors'; import { - BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, SyntaxNode, + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; import { SyntaxToken } from '@/core/lexer/tokens'; import { ElementValidator } from '@/core/analyzer/validator/types'; @@ -36,6 +36,9 @@ export default class ProjectValidator implements ElementValidator { if (!nameNode) { return []; } + if (nameNode instanceof WildcardNode) { + return [new CompileError(CompileErrorCode.INVALID_NAME, 'Wildcard (*) is not allowed as a Project name', nameNode)]; + } if (!isSimpleName(nameNode)) { return [new CompileError(CompileErrorCode.INVALID_NAME, 'A Project\'s name is optional or must be an identifier or a quoted identifer', nameNode)]; diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/records.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/records.ts index 22da5b517..8d87b6a01 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/records.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/records.ts @@ -2,7 +2,7 @@ import { partition } from 'lodash-es'; import SymbolFactory from '@/core/analyzer/symbol/factory'; import { CompileError, CompileErrorCode } from '@/core/errors'; import { - BlockExpressionNode, CallExpressionNode, CommaExpressionNode, ElementDeclarationNode, EmptyNode, FunctionApplicationNode, FunctionExpressionNode, ListExpressionNode, ProgramNode, SyntaxNode, + BlockExpressionNode, CallExpressionNode, CommaExpressionNode, ElementDeclarationNode, EmptyNode, FunctionApplicationNode, FunctionExpressionNode, ListExpressionNode, ProgramNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; import { SyntaxToken } from '@/core/lexer/tokens'; import { ElementValidator } from '@/core/analyzer/validator/types'; @@ -59,6 +59,9 @@ export default class RecordsValidator implements ElementValidator { } private validateName (nameNode?: SyntaxNode): CompileError[] { + if (nameNode instanceof WildcardNode) { + return [new CompileError(CompileErrorCode.INVALID_NAME, 'Wildcard (*) is not allowed as a Records name', nameNode)]; + } const parent = this.declarationNode.parent; const isTopLevel = parent instanceof ProgramNode; diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/ref.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/ref.ts index cb2a999ad..537e7dd6f 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/ref.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/ref.ts @@ -3,7 +3,7 @@ import { SyntaxToken, SyntaxTokenKind } from '@/core/lexer/tokens'; import SymbolFactory from '@/core/analyzer/symbol/factory'; import { CompileError, CompileErrorCode } from '@/core/errors'; import { - BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, IdentiferStreamNode, ListExpressionNode, ProgramNode, SyntaxNode, + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, IdentiferStreamNode, ListExpressionNode, ProgramNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; import { extractStringFromIdentifierStream, @@ -46,6 +46,9 @@ export default class RefValidator implements ElementValidator { if (!nameNode) { return []; } + if (nameNode instanceof WildcardNode) { + return [new CompileError(CompileErrorCode.INVALID_NAME, 'Wildcard (*) is not allowed as a Ref name', nameNode)]; + } if (!isSimpleName(nameNode)) { return [new CompileError(CompileErrorCode.INVALID_NAME, 'A Ref\'s name is optional or must be an identifier or a quoted identifer', nameNode)]; diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/table.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/table.ts index 56443d91f..f4cb8b1f9 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/table.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/table.ts @@ -14,6 +14,7 @@ import { PrefixExpressionNode, PrimaryExpressionNode, SyntaxNode, + WildcardNode, } from '@/core/parser/nodes'; import { destructureComplexVariable, extractVariableFromExpression, extractVarNameFromPrimaryVariable } from '@/core/analyzer/utils'; import { @@ -79,6 +80,9 @@ export default class TableValidator implements ElementValidator { if (!nameNode) { return [new CompileError(CompileErrorCode.NAME_NOT_FOUND, 'A Table must have a name', this.declarationNode)]; } + if (nameNode instanceof WildcardNode) { + return [new CompileError(CompileErrorCode.INVALID_NAME, 'Wildcard (*) is not allowed as a Table name', nameNode)]; + } if (nameNode instanceof ArrayNode) { return [new CompileError(CompileErrorCode.INVALID_NAME, 'Invalid array as Table name, maybe you forget to add a space between the name and the setting list?', nameNode)]; } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tableGroup.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tableGroup.ts index c63c8e6d0..8b26a6b95 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tableGroup.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tableGroup.ts @@ -7,7 +7,7 @@ import { ElementValidator } from '@/core/analyzer/validator/types'; import SymbolTable from '@/core/analyzer/symbol/symbolTable'; import { SyntaxToken } from '@/core/lexer/tokens'; import { - BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, SyntaxNode, + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; import SymbolFactory from '@/core/analyzer/symbol/factory'; import { createTableGroupFieldSymbolIndex, createTableGroupSymbolIndex } from '@/core/analyzer/symbol/symbolIndex'; @@ -56,6 +56,9 @@ export default class TableGroupValidator implements ElementValidator { this.declarationNode, )]; } + if (nameNode instanceof WildcardNode) { + return [new CompileError(CompileErrorCode.INVALID_NAME, 'Wildcard (*) is not allowed as a TableGroup name', nameNode)]; + } if (!isSimpleName(nameNode)) { return [new CompileError( CompileErrorCode.INVALID_NAME, diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tablePartial.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tablePartial.ts index 2372cfa66..9ab60974b 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tablePartial.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tablePartial.ts @@ -11,6 +11,7 @@ import { ListExpressionNode, PrimaryExpressionNode, SyntaxNode, + WildcardNode, } from '@/core/parser/nodes'; import { destructureComplexVariable, extractVarNameFromPrimaryVariable } from '@/core/analyzer/utils'; import { @@ -83,6 +84,9 @@ export default class TablePartialValidator implements ElementValidator { this.declarationNode, )]; } + if (nameNode instanceof WildcardNode) { + return [new CompileError(CompileErrorCode.INVALID_NAME, 'Wildcard (*) is not allowed as a TablePartial name', nameNode)]; + } if (!isSimpleName(nameNode)) { return [new CompileError( CompileErrorCode.INVALID_NAME, diff --git a/packages/dbml-parse/src/core/lexer/lexer.ts b/packages/dbml-parse/src/core/lexer/lexer.ts index 71827d5c1..2de707af9 100644 --- a/packages/dbml-parse/src/core/lexer/lexer.ts +++ b/packages/dbml-parse/src/core/lexer/lexer.ts @@ -168,6 +168,9 @@ export default class Lexer { this.operator(c); } break; + case '*': + this.addToken(SyntaxTokenKind.WILDCARD); + break; default: if (isOp(c)) { this.operator(c); diff --git a/packages/dbml-parse/src/core/lexer/tokens.ts b/packages/dbml-parse/src/core/lexer/tokens.ts index 64ba3d4cf..23fc07a4d 100644 --- a/packages/dbml-parse/src/core/lexer/tokens.ts +++ b/packages/dbml-parse/src/core/lexer/tokens.ts @@ -30,6 +30,7 @@ export enum SyntaxTokenKind { SINGLE_LINE_COMMENT = '', MULTILINE_COMMENT = '', + WILDCARD = '', } export function isTriviaToken (token: SyntaxToken): boolean { @@ -53,7 +54,6 @@ export function isOp (c?: string): boolean { switch (c) { case '+': case '-': - case '*': case '/': case '%': case '<': diff --git a/packages/dbml-parse/src/core/parser/nodes.ts b/packages/dbml-parse/src/core/parser/nodes.ts index d842734ca..89e954547 100644 --- a/packages/dbml-parse/src/core/parser/nodes.ts +++ b/packages/dbml-parse/src/core/parser/nodes.ts @@ -99,6 +99,7 @@ export enum SyntaxNodeKind { PRIMARY_EXPRESSION = '', GROUP_EXPRESSION = '', COMMA_EXPRESSION = '', + WILDCARD = '', EMPTY = '', ARRAY = '', } @@ -566,6 +567,16 @@ export class LiteralNode extends SyntaxNode { } } +// A wildcard (*) expression used in DiagramView blocks +export class WildcardNode extends SyntaxNode { + token?: SyntaxToken; + + constructor ({ token }: { token?: SyntaxToken }, id: SyntaxNodeId) { + super(id, SyntaxNodeKind.WILDCARD, [token]); + this.token = token; + } +} + // Form: | // A variable reference // e.g. users diff --git a/packages/dbml-parse/src/core/parser/parser.ts b/packages/dbml-parse/src/core/parser/parser.ts index fd2657b33..5e9be0b50 100644 --- a/packages/dbml-parse/src/core/parser/parser.ts +++ b/packages/dbml-parse/src/core/parser/parser.ts @@ -33,6 +33,7 @@ import { SyntaxNodeIdGenerator, TupleExpressionNode, VariableNode, + WildcardNode, } from '@/core/parser/nodes'; import NodeFactory from '@/core/parser/factory'; import { hasTrailingNewLines, hasTrailingSpaces, isAtStartOfLine } from '@/core/lexer/utils'; @@ -656,14 +657,7 @@ export default class Parser { private leftExpression_bp (): NormalExpressionNode { let leftExpression: NormalExpressionNode | undefined; - // Allow * as a wildcard identifier (e.g. DiagramView Tables { * }) - // Must be handled before the isOpToken branch to avoid "Unexpected '*'" error - if (this.check(SyntaxTokenKind.OP) && this.peek().value === '*') { - this.advance(); - leftExpression = this.nodeFactory.create(PrimaryExpressionNode, { - expression: this.nodeFactory.create(VariableNode, { variable: this.previous() }), - }); - } else if (isOpToken(this.peek())) { + if (isOpToken(this.peek())) { const args: { op?: SyntaxToken; expression?: NormalExpressionNode; @@ -726,7 +720,15 @@ export default class Parser { | BlockExpressionNode | TupleExpressionNode | FunctionExpressionNode - | GroupExpressionNode { + | GroupExpressionNode + | WildcardNode { + if (this.check(SyntaxTokenKind.WILDCARD)) { + this.advance(); + return this.nodeFactory.create(WildcardNode, { + token: this.previous(), + }); + } + if ( this.check( SyntaxTokenKind.NUMERIC_LITERAL, @@ -1203,7 +1205,6 @@ const infixBindingPowerMap: { [index: string]: { left: number; right: number } | undefined; } = { '+': { left: 9, right: 10 }, - '*': { left: 11, right: 12 }, '-': { left: 9, right: 10 }, '/': { left: 11, right: 12 }, '%': { left: 11, right: 12 }, diff --git a/packages/dbml-parse/src/core/parser/utils.ts b/packages/dbml-parse/src/core/parser/utils.ts index c605a3b3f..f0e91154b 100644 --- a/packages/dbml-parse/src/core/parser/utils.ts +++ b/packages/dbml-parse/src/core/parser/utils.ts @@ -27,6 +27,7 @@ import { SyntaxNode, TupleExpressionNode, VariableNode, + WildcardNode, } from '@/core/parser/nodes'; import { destructureComplexVariable } from '@/core/analyzer/utils'; @@ -305,6 +306,10 @@ export function getMemberChain (node: SyntaxNode): Readonly<(SyntaxNode | Syntax ); } + if (node instanceof WildcardNode) { + return filterUndefined(node.token); + } + if (node instanceof GroupExpressionNode) { throw new Error('This case is already handled by TupleExpressionNode'); } @@ -363,10 +368,7 @@ export function isExpressionAVariableNode ( // Return true if an expression node is a wildcard (*) export function isWildcardExpression (node: SyntaxNode | undefined): boolean { if (!node) return false; - if (node instanceof PrimaryExpressionNode && node.expression instanceof VariableNode) { - return node.expression.variable?.value === '*'; - } - return false; + return node instanceof WildcardNode; } // Return true if an expression node is a primary expression From 9d2f0d667366d3592e6c151eaea43b241af78ac8 Mon Sep 17 00:00:00 2001 From: NQPhuc <11730168+NQPhuc@users.noreply.github.com> Date: Tue, 31 Mar 2026 14:10:19 +0700 Subject: [PATCH 26/31] fix: export sync diagram functions under complier + avoid parsing multiple time per diagram sync call --- packages/dbml-core/src/index.ts | 8 +- packages/dbml-core/src/transform/index.js | 26 ++++ packages/dbml-core/types/index.d.ts | 7 +- packages/dbml-core/types/transform/index.d.ts | 12 ++ packages/dbml-parse/src/compiler/index.ts | 17 ++- .../src/compiler/queries/transform/index.ts | 1 + .../queries/transform/syncDiagramView.ts | 118 +++++++++++------- packages/dbml-parse/src/index.ts | 6 +- 8 files changed, 139 insertions(+), 56 deletions(-) diff --git a/packages/dbml-core/src/index.ts b/packages/dbml-core/src/index.ts index 3bbf6ca56..90aa56844 100644 --- a/packages/dbml-core/src/index.ts +++ b/packages/dbml-core/src/index.ts @@ -5,6 +5,8 @@ import importer from './import'; import exporter from './export'; import { renameTable, + syncDiagramView, + findDiagramViewBlocks, } from './transform'; import { VERSION } from './utils/version'; @@ -12,6 +14,8 @@ export { importer, exporter, renameTable, + syncDiagramView, + findDiagramViewBlocks, ModelExporter, CompilerError, Parser, @@ -36,8 +40,6 @@ export { tryExtractEnum, addDoubleQuoteIfNeeded, formatRecordValue, - // DiagramView exports - syncDiagramView, // Monaco editor syntax highlighting dbmlMonarchTokensProvider, } from '@dbml/parse'; @@ -45,6 +47,8 @@ export { // Re-export types export type { DiagramViewSyncOperation, + DiagramViewBlock, DiagramView, FilterConfig, + TextEdit, } from '@dbml/parse'; diff --git a/packages/dbml-core/src/transform/index.js b/packages/dbml-core/src/transform/index.js index 7505936c2..31265ce82 100644 --- a/packages/dbml-core/src/transform/index.js +++ b/packages/dbml-core/src/transform/index.js @@ -23,3 +23,29 @@ export function renameTable (oldName, newName, dbmlCode) { compiler.setSource(dbmlCode); return compiler.renameTable(oldName, newName); } + +/** + * Synchronizes DiagramView blocks in DBML source code. + * + * @param {string} dbmlCode - The DBML source code + * @param {import('@dbml/parse').DiagramViewSyncOperation[]} operations - Operations to apply + * @param {import('@dbml/parse').DiagramViewBlock[]} [blocks] - Optional pre-parsed blocks + * @returns {{ newDbml: string, edits: import('@dbml/parse').TextEdit[] }} + */ +export function syncDiagramView (dbmlCode, operations, blocks) { + const compiler = new Compiler(); + compiler.setSource(dbmlCode); + return compiler.syncDiagramView(operations, blocks); +} + +/** + * Finds all DiagramView blocks in DBML source code. + * + * @param {string} dbmlCode - The DBML source code + * @returns {import('@dbml/parse').DiagramViewBlock[]} + */ +export function findDiagramViewBlocks (dbmlCode) { + const compiler = new Compiler(); + compiler.setSource(dbmlCode); + return compiler.findDiagramViewBlocks(); +} diff --git a/packages/dbml-core/types/index.d.ts b/packages/dbml-core/types/index.d.ts index acf6e5bd2..83f0d131e 100644 --- a/packages/dbml-core/types/index.d.ts +++ b/packages/dbml-core/types/index.d.ts @@ -4,9 +4,13 @@ import importer from './import'; import exporter from './export'; import { renameTable, + syncDiagramView, + findDiagramViewBlocks, } from './transform'; export { renameTable, + syncDiagramView, + findDiagramViewBlocks, importer, exporter, ModelExporter, @@ -36,6 +40,5 @@ export { tryExtractEnum, addDoubleQuoteIfNeeded, formatRecordValue, - syncDiagramView, } from '@dbml/parse'; -export type { DiagramView, DiagramViewSyncOperation, FilterConfig } from '@dbml/parse'; +export type { DiagramView, DiagramViewSyncOperation, DiagramViewBlock, FilterConfig, TextEdit } from '@dbml/parse'; diff --git a/packages/dbml-core/types/transform/index.d.ts b/packages/dbml-core/types/transform/index.d.ts index 0cf165118..56f813a1a 100644 --- a/packages/dbml-core/types/transform/index.d.ts +++ b/packages/dbml-core/types/transform/index.d.ts @@ -1,3 +1,5 @@ +import type { DiagramViewSyncOperation, DiagramViewBlock, TextEdit } from '@dbml/parse'; + export type TableNameInput = string | { schema?: string; table: string }; export function renameTable( @@ -5,3 +7,13 @@ export function renameTable( newName: TableNameInput, dbmlCode: string ): string; + +export function syncDiagramView( + dbmlCode: string, + operations: DiagramViewSyncOperation[], + blocks?: DiagramViewBlock[], +): { newDbml: string; edits: TextEdit[] }; + +export function findDiagramViewBlocks( + dbmlCode: string, +): DiagramViewBlock[]; diff --git a/packages/dbml-parse/src/compiler/index.ts b/packages/dbml-parse/src/compiler/index.ts index 351f9f1fa..22a463ff2 100644 --- a/packages/dbml-parse/src/compiler/index.ts +++ b/packages/dbml-parse/src/compiler/index.ts @@ -15,14 +15,18 @@ import { containerStack, containerToken, containerElement, containerScope, conta import { renameTable, applyTextEdits, + syncDiagramView, + findDiagramViewBlocks, type TextEdit, type TableNameInput, + type DiagramViewSyncOperation, + type DiagramViewBlock, } from './queries/transform'; import { splitQualifiedIdentifier, unescapeString, escapeString, formatRecordValue, isValidIdentifier, addDoubleQuoteIfNeeded } from './queries/utils'; // Re-export types export { ScopeKind } from './types'; -export type { TextEdit, TableNameInput }; +export type { TextEdit, TableNameInput, DiagramViewSyncOperation, DiagramViewBlock }; // Re-export utilities export { splitQualifiedIdentifier, unescapeString, escapeString, formatRecordValue, isValidIdentifier, addDoubleQuoteIfNeeded }; @@ -89,6 +93,17 @@ export default class Compiler { return renameTable.call(this, oldName, newName); } + syncDiagramView ( + operations: DiagramViewSyncOperation[], + blocks?: DiagramViewBlock[], + ): { newDbml: string; edits: TextEdit[] } { + return syncDiagramView(this.parse.source(), operations, blocks); + } + + findDiagramViewBlocks (): DiagramViewBlock[] { + return findDiagramViewBlocks(this.parse.source()); + } + applyTextEdits (edits: TextEdit[]): string { return applyTextEdits(this.parse.source(), edits); } diff --git a/packages/dbml-parse/src/compiler/queries/transform/index.ts b/packages/dbml-parse/src/compiler/queries/transform/index.ts index a2876f8b1..76f0bdf16 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/index.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/index.ts @@ -1,3 +1,4 @@ export { renameTable } from './renameTable'; export { applyTextEdits, type TextEdit } from './applyTextEdits'; export { type TableNameInput } from './utils'; +export { syncDiagramView, findDiagramViewBlocks, type DiagramViewSyncOperation, type DiagramViewBlock } from './syncDiagramView'; diff --git a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts index 2d6f895e7..da788e8e2 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts @@ -16,13 +16,13 @@ export interface DiagramViewSyncOperation { }; } -interface DiagramViewBlock { +export interface DiagramViewBlock { name: string; startIndex: number; endIndex: number; } -function findDiagramViewBlocks (source: string): DiagramViewBlock[] { +export function findDiagramViewBlocks (source: string): DiagramViewBlock[] { const blocks: DiagramViewBlock[] = []; const lexerResult = new Lexer(source).lex(); if (lexerResult.getErrors().length > 0) return blocks; @@ -131,59 +131,77 @@ function generateDiagramViewBlock ( * * @param dbml - The original DBML source code * @param operations - Array of operations to apply (create, update, delete) - * @returns Object containing the new DBML source code + * @param blocks - Optional pre-parsed blocks from findDiagramViewBlocks. If not provided, parses internally. + * @returns Object containing the new DBML source code and the text edits applied */ export function syncDiagramView ( dbml: string, operations: DiagramViewSyncOperation[], -): { newDbml: string } { + blocks?: DiagramViewBlock[], +): { newDbml: string; edits: TextEdit[] } { + let currentBlocks = blocks ?? findDiagramViewBlocks(dbml); let currentDbml = dbml; + const allEdits: TextEdit[] = []; for (const op of operations) { - currentDbml = applyOperation(currentDbml, op); + const { dbml: newDbml, edits } = applyOperation(currentDbml, op, currentBlocks); + allEdits.push(...edits); + currentDbml = newDbml; + // Re-parse blocks after each operation since positions shifted + if (edits.length > 0) { + currentBlocks = findDiagramViewBlocks(currentDbml); + } } - return { newDbml: currentDbml }; + return { newDbml: currentDbml, edits: allEdits }; } -function applyOperation (dbml: string, operation: DiagramViewSyncOperation): string { +function applyOperation ( + dbml: string, + operation: DiagramViewSyncOperation, + blocks: DiagramViewBlock[], +): { dbml: string; edits: TextEdit[] } { switch (operation.operation) { case 'create': - return applyCreate(dbml, operation); - case 'update': { - const blocks = findDiagramViewBlocks(dbml); + return applyCreate(dbml, operation, blocks); + case 'update': return applyUpdate(dbml, operation, blocks); - } - case 'delete': { - const blocks = findDiagramViewBlocks(dbml); + case 'delete': return applyDelete(dbml, operation, blocks); - } default: - return dbml; + return { dbml, edits: [] }; } } -function applyCreate (dbml: string, operation: DiagramViewSyncOperation): string { +function applyCreate ( + dbml: string, + operation: DiagramViewSyncOperation, + blocks: DiagramViewBlock[], +): { dbml: string; edits: TextEdit[] } { // If a block with this name already exists, treat as update to avoid duplicate blocks - const blocks = findDiagramViewBlocks(dbml); const existing = blocks.find((b) => b.name === operation.name); if (existing) { return applyUpdate(dbml, operation, blocks); } const newBlock = generateDiagramViewBlock(operation.name, operation.visibleEntities); + const appendText = '\n\n' + newBlock + '\n'; + const edit: TextEdit = { + start: dbml.length, + end: dbml.length, + newText: appendText, + }; - // Append at end of file - return dbml.trimEnd() + '\n\n' + newBlock + '\n'; + return { dbml: dbml + appendText, edits: [edit] }; } function applyUpdate ( dbml: string, operation: DiagramViewSyncOperation, blocks: DiagramViewBlock[], -): string { +): { dbml: string; edits: TextEdit[] } { const block = blocks.find((b) => b.name === operation.name); - if (!block) return dbml; + if (!block) return { dbml, edits: [] }; const edits: TextEdit[] = []; @@ -200,43 +218,47 @@ function applyUpdate ( }); } - return applyTextEdits(dbml, edits); + return { dbml: applyTextEdits(dbml, edits), edits }; } function applyDelete ( dbml: string, operation: DiagramViewSyncOperation, blocks: DiagramViewBlock[], -): string { +): { dbml: string; edits: TextEdit[] } { const block = blocks.find((b) => b.name === operation.name); - if (!block) return dbml; - - // Remove block and surrounding whitespace - const lines = dbml.split('\n'); - let startLine = 0; - let endLine = lines.length - 1; - - // Find line boundaries - let currentPos = 0; - for (let i = 0; i < lines.length; i++) { - const lineStart = currentPos; - const lineEnd = currentPos + lines[i].length; - - if (lineStart <= block.startIndex && block.startIndex <= lineEnd) { - startLine = i; + if (!block) return { dbml, edits: [] }; + + // Expand range to include surrounding whitespace/newlines + let start = block.startIndex; + let end = block.endIndex; + + // Expand backwards to consume preceding blank lines + while (start > 0 && dbml[start - 1] === '\n') { + start--; + // Also consume \r for CRLF + if (start > 0 && dbml[start - 1] === '\r') { + start--; } - if (lineStart <= block.endIndex && block.endIndex <= lineEnd) { - endLine = i; + // Check if the line before is blank — if not, stop + const prevLineStart = dbml.lastIndexOf('\n', start - 1) + 1; + const prevLine = dbml.substring(prevLineStart, start); + if (prevLine.trim() !== '') { + // Not a blank line, restore position + start = block.startIndex; + // But still consume one newline before the block + if (start > 0 && dbml[start - 1] === '\n') { + start--; + if (start > 0 && dbml[start - 1] === '\r') start--; + } + break; } - - currentPos = lineEnd + 1; // +1 for newline } - // Remove lines and clean up extra blank lines - const newLines = [ - ...lines.slice(0, startLine), - ...lines.slice(endLine + 1), - ]; + // Expand forward to consume trailing newline + if (end < dbml.length && dbml[end] === '\r') end++; + if (end < dbml.length && dbml[end] === '\n') end++; - return newLines.join('\n'); + const edit: TextEdit = { start, end, newText: '' }; + return { dbml: applyTextEdits(dbml, [edit]), edits: [edit] }; } diff --git a/packages/dbml-parse/src/index.ts b/packages/dbml-parse/src/index.ts index 97c89bc04..f2c783105 100644 --- a/packages/dbml-parse/src/index.ts +++ b/packages/dbml-parse/src/index.ts @@ -60,9 +60,9 @@ export { type FilterConfig, } from '@/core/interpreter/types'; -// Export syncDiagramView transform -export { syncDiagramView } from '@/compiler/queries/transform/syncDiagramView'; -export type { DiagramViewSyncOperation } from '@/compiler/queries/transform/syncDiagramView'; +// DiagramView types (methods exposed via Compiler) +export type { DiagramViewSyncOperation, DiagramViewBlock } from '@/compiler/queries/transform/syncDiagramView'; +export type { TextEdit } from '@/compiler/queries/transform/applyTextEdits'; export { dbmlMonarchTokensProvider } from '@/services/monarch'; From 3291cae3b765bc8c1405878267d8b3eacc275399 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Tue, 31 Mar 2026 16:01:40 +0700 Subject: [PATCH 27/31] fix(dbml-parse): address PR #849 review comments (items 3-14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Item 3: Add warnings support to validator pipeline via { errors, warnings } return type on ElementValidator - Item 4: Validate wildcard args — flag error on `* ` - Item 5: Move registerSubBlockFields into validateSubBlock - Item 6: Add DiagramViewSymbol/DiagramViewFieldSymbol to generatePossibleIndexes and getSymbolKind - Item 7: Fix bindSchemaReferences to bind all nested schema segments - Items 8+14: Move _explicitWildcards/_explicitlySet from DiagramView to InterpreterDatabase Maps, make diagramViews non-optional - Item 9: Use ElementKind.DiagramView instead of string literal - Item 10: Replace manual symbol table iteration with suggestNamesInScope - Item 12: Use DEFAULT_SCHEMA_NAME constant in syncDiagramView --- AGENTS.md | 2 +- CLAUDE.md | 2 +- .../queries/transform/syncDiagramView.ts | 3 +- .../binder/elementBinder/diagramView.ts | 9 +- .../src/core/analyzer/symbol/utils.ts | 12 +++ .../validator/elementValidators/checks.ts | 23 ++--- .../validator/elementValidators/custom.ts | 21 +++-- .../elementValidators/diagramView.ts | 75 +++++++++------- .../validator/elementValidators/enum.ts | 25 +++--- .../validator/elementValidators/indexes.ts | 23 ++--- .../validator/elementValidators/note.ts | 23 ++--- .../validator/elementValidators/project.ts | 11 ++- .../validator/elementValidators/records.ts | 11 ++- .../validator/elementValidators/ref.ts | 23 ++--- .../validator/elementValidators/table.ts | 25 +++--- .../validator/elementValidators/tableGroup.ts | 25 +++--- .../elementValidators/tablePartial.ts | 25 +++--- .../src/core/analyzer/validator/types.ts | 4 +- .../src/core/analyzer/validator/validator.ts | 9 +- .../elementInterpreter/diagramView.ts | 32 ++++--- .../src/core/interpreter/interpreter.ts | 20 ++--- .../dbml-parse/src/core/interpreter/types.ts | 8 +- .../src/services/suggestions/provider.ts | 89 +++++-------------- 23 files changed, 250 insertions(+), 250 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7fbaf02d0..ca6662a3d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **dbml** (7342 symbols, 18681 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **dbml** (7344 symbols, 18708 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. diff --git a/CLAUDE.md b/CLAUDE.md index 7fbaf02d0..ca6662a3d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **dbml** (7342 symbols, 18681 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **dbml** (7344 symbols, 18708 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. diff --git a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts index da788e8e2..3b9fc7db1 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts @@ -3,6 +3,7 @@ import Lexer from '@/core/lexer/lexer'; import Parser from '@/core/parser/parser'; import { SyntaxNodeIdGenerator } from '@/core/parser/nodes'; import { destructureComplexVariable } from '@/core/analyzer/utils'; +import { DEFAULT_SCHEMA_NAME } from '@/constants'; export interface DiagramViewSyncOperation { operation: 'create' | 'update' | 'delete'; @@ -75,7 +76,7 @@ function generateDiagramViewBlock ( lines.push(' Tables { * }'); } else { const tableNames = visibleEntities.tables.map((t) => - t.schemaName === 'public' ? t.name : `${t.schemaName}.${t.name}`, + t.schemaName === DEFAULT_SCHEMA_NAME ? t.name : `${t.schemaName}.${t.name}`, ); lines.push(' Tables {'); tableNames.forEach((n) => lines.push(` ${n}`)); diff --git a/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts b/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts index 4e44c9137..1758cfa8b 100644 --- a/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts +++ b/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts @@ -178,14 +178,7 @@ export default class DiagramViewBinder implements ElementBinder { const bindees = scanNonListNodeForBinding(field.callee); return bindees.flatMap((bindee) => { - const schemaBindee = bindee.variables.pop(); - if (!schemaBindee) { - return []; - } - - return lookupAndBindInScope(this.ast, [ - { node: schemaBindee, kind: SymbolKind.Schema }, - ]); + return lookupAndBindInScope(this.ast, bindee.variables.map((b) => ({ node: b, kind: SymbolKind.Schema }))); }); }); } diff --git a/packages/dbml-parse/src/core/analyzer/symbol/utils.ts b/packages/dbml-parse/src/core/analyzer/symbol/utils.ts index 8ab83d913..09409f5b4 100644 --- a/packages/dbml-parse/src/core/analyzer/symbol/utils.ts +++ b/packages/dbml-parse/src/core/analyzer/symbol/utils.ts @@ -4,6 +4,8 @@ import { createColumnSymbolIndex, createEnumFieldSymbolIndex, createEnumSymbolIndex, + createDiagramViewFieldSymbolIndex, + createDiagramViewSymbolIndex, createPartialInjectionSymbolIndex, createSchemaSymbolIndex, createTableGroupFieldSymbolIndex, @@ -24,6 +26,8 @@ import { TablePartialSymbol, PartialInjectionSymbol, StickyNoteSymbol, + DiagramViewSymbol, + DiagramViewFieldSymbol, } from './symbols'; // Given `name`, generate indexes with `name` and all possible kind @@ -39,6 +43,8 @@ export function generatePossibleIndexes (name: string): NodeSymbolIndex[] { createTableGroupFieldSymbolIndex, createTablePartialSymbolIndex, createPartialInjectionSymbolIndex, + createDiagramViewSymbolIndex, + createDiagramViewFieldSymbolIndex, ].map((f) => f(name)); } @@ -76,5 +82,11 @@ export function getSymbolKind (symbol: NodeSymbol): SymbolKind { if (symbol instanceof StickyNoteSymbol) { return SymbolKind.Note; } + if (symbol instanceof DiagramViewSymbol) { + return SymbolKind.DiagramView; + } + if (symbol instanceof DiagramViewFieldSymbol) { + return SymbolKind.DiagramViewField; + } throw new Error('No other possible symbol kind in getSymbolKind'); } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/checks.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/checks.ts index 221722078..b2aa983aa 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/checks.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/checks.ts @@ -1,6 +1,6 @@ import { last, partition } from 'lodash-es'; import SymbolFactory from '@/core/analyzer/symbol/factory'; -import { CompileError, CompileErrorCode } from '@/core/errors'; +import { CompileError, CompileErrorCode, CompileWarning } from '@/core/errors'; import { BlockExpressionNode, ElementDeclarationNode, @@ -30,14 +30,17 @@ export default class ChecksValidator implements ElementValidator { this.symbolFactory = symbolFactory; } - validate (): CompileError[] { - return [ - ...this.validateContext(), - ...this.validateName(this.declarationNode.name), - ...this.validateAlias(this.declarationNode.alias), - ...this.validateSettingList(this.declarationNode.attributeList), - ...this.validateBody(this.declarationNode.body), - ]; + validate (): { errors: CompileError[], warnings: CompileWarning[] } { + return { + errors: [ + ...this.validateContext(), + ...this.validateName(this.declarationNode.name), + ...this.validateAlias(this.declarationNode.alias), + ...this.validateSettingList(this.declarationNode.attributeList), + ...this.validateBody(this.declarationNode.body), + ], + warnings: [], + }; } private validateContext (): CompileError[] { @@ -146,7 +149,7 @@ export default class ChecksValidator implements ElementValidator { } const _Validator = pickValidator(sub as ElementDeclarationNode & { type: SyntaxToken }); const validator = new _Validator(sub as ElementDeclarationNode & { type: SyntaxToken }, this.publicSymbolTable, this.symbolFactory); - return validator.validate(); + return validator.validate().errors; }); } } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/custom.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/custom.ts index c6f0ae740..8fd6de396 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/custom.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/custom.ts @@ -1,4 +1,4 @@ -import { CompileError, CompileErrorCode } from '@/core/errors'; +import { CompileError, CompileErrorCode, CompileWarning } from '@/core/errors'; import { BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, ProgramNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; @@ -21,14 +21,17 @@ export default class CustomValidator implements ElementValidator { this.symbolFactory = symbolFactory; } - validate (): CompileError[] { - return [ - ...this.validateContext(), - ...this.validateName(this.declarationNode.name), - ...this.validateAlias(this.declarationNode.alias), - ...this.validateSettingList(this.declarationNode.attributeList), - ...this.validateBody(this.declarationNode.body), - ]; + validate (): { errors: CompileError[], warnings: CompileWarning[] } { + return { + errors: [ + ...this.validateContext(), + ...this.validateName(this.declarationNode.name), + ...this.validateAlias(this.declarationNode.alias), + ...this.validateSettingList(this.declarationNode.attributeList), + ...this.validateBody(this.declarationNode.body), + ], + warnings: [], + }; } private validateContext (): CompileError[] { diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts index 8c9aa5ade..0301da437 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/diagramView.ts @@ -27,15 +27,17 @@ export default class DiagramViewValidator implements ElementValidator { this.symbolFactory = symbolFactory; } - validate (): CompileError[] { - return [ + validate (): { errors: CompileError[], warnings: CompileWarning[] } { + const errors: CompileError[] = [ ...this.validateContext(), ...this.validateName(this.declarationNode.name), ...this.validateAlias(this.declarationNode.alias), ...this.validateSettingList(this.declarationNode.attributeList), ...this.registerElement(), - ...this.validateBody(this.declarationNode.body) as CompileError[], ]; + const bodyResult = this.validateBody(this.declarationNode.body); + errors.push(...bodyResult.errors); + return { errors, warnings: bodyResult.warnings }; } private validateContext (): CompileError[] { @@ -113,31 +115,42 @@ export default class DiagramViewValidator implements ElementValidator { return errors; } - validateBody (body?: FunctionApplicationNode | BlockExpressionNode): (CompileError | CompileWarning)[] { - if (!body) return []; + validateBody (body?: FunctionApplicationNode | BlockExpressionNode): { errors: CompileError[], warnings: CompileWarning[] } { + if (!body) return { errors: [], warnings: [] }; if (body instanceof FunctionApplicationNode) { - return [new CompileError( - CompileErrorCode.UNEXPECTED_SIMPLE_BODY, - 'A DiagramView\'s body must be a block', - body, - )]; + return { + errors: [new CompileError( + CompileErrorCode.UNEXPECTED_SIMPLE_BODY, + 'A DiagramView\'s body must be a block', + body, + )], + warnings: [], + }; } const [fields, subs] = partition(body.body, (e) => e instanceof FunctionApplicationNode); - return [ - ...this.validateFields(fields as FunctionApplicationNode[]), - ...this.validateSubElements(subs as ElementDeclarationNode[]), - ]; + const subResult = this.validateSubElements(subs as ElementDeclarationNode[]); + return { + errors: [...this.validateFields(fields as FunctionApplicationNode[]), ...subResult.errors], + warnings: subResult.warnings, + }; } - validateFields (fields: FunctionApplicationNode[]): (CompileError | CompileWarning)[] { + validateFields (fields: FunctionApplicationNode[]): CompileError[] { return fields.flatMap((field) => { // Body-level {*} is valid shorthand for "show all entities" if (isWildcardExpression(field.callee)) { + if (field.args.length > 0) { + return field.args.map((arg) => new CompileError( + CompileErrorCode.INVALID_DIAGRAMVIEW_FIELD, + 'DiagramView field should only have a single name', + arg, + )); + } return []; } - const errors: (CompileError | CompileWarning)[] = []; + const errors: CompileError[] = []; // Fields at the top level of DiagramView are not allowed errors.push(new CompileError( CompileErrorCode.INVALID_DIAGRAMVIEW_FIELD, @@ -148,10 +161,10 @@ export default class DiagramViewValidator implements ElementValidator { }); } - private validateSubElements (subs: ElementDeclarationNode[]): (CompileError | CompileWarning)[] { - const errors: (CompileError | CompileWarning)[] = []; + private validateSubElements (subs: ElementDeclarationNode[]): { errors: CompileError[], warnings: CompileWarning[] } { + const errors: CompileError[] = []; + const warnings: CompileWarning[] = []; - // Validate allowed sub-blocks: Tables, Notes, TableGroups, Schemas const allowedBlocks = ['tables', 'notes', 'tablegroups', 'schemas']; for (const sub of subs) { @@ -170,26 +183,24 @@ export default class DiagramViewValidator implements ElementValidator { continue; } - // Validate the sub-block body - errors.push(...this.validateSubBlock(sub)); - - // Register fields in the sub-block - errors.push(...this.registerSubBlockFields(sub)); + const subBlockResult = this.validateSubBlock(sub); + errors.push(...subBlockResult.errors); + warnings.push(...subBlockResult.warnings); } - return errors; + return { errors, warnings }; } - private validateSubBlock (sub: ElementDeclarationNode): (CompileError | CompileWarning)[] { - const errors: (CompileError | CompileWarning)[] = []; + private validateSubBlock (sub: ElementDeclarationNode): { errors: CompileError[], warnings: CompileWarning[] } { + const errors: CompileError[] = []; + const warnings: CompileWarning[] = []; if (!sub.body || !(sub.body instanceof BlockExpressionNode)) { - return errors; + return { errors, warnings }; } const body = sub.body as BlockExpressionNode; - // Check for * combined with specific items (warning) const hasWildcard = body.body.some( (e) => e instanceof FunctionApplicationNode && isWildcardExpression(e.callee), ); @@ -198,14 +209,16 @@ export default class DiagramViewValidator implements ElementValidator { ); if (hasWildcard && hasSpecificItems) { - errors.push(new CompileWarning( + warnings.push(new CompileWarning( CompileErrorCode.INVALID_DIAGRAMVIEW_FIELD, `Wildcard (*) combined with specific items in ${sub.type?.value} block. Specific items will be ignored.`, sub, )); } - return errors; + errors.push(...this.registerSubBlockFields(sub)); + + return { errors, warnings }; } registerSubBlockFields (sub: ElementDeclarationNode): CompileError[] { diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/enum.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/enum.ts index 5a01ec412..1417c4804 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/enum.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/enum.ts @@ -1,7 +1,7 @@ import { DEFAULT_SCHEMA_NAME } from '@/constants'; import { last, partition } from 'lodash-es'; import SymbolFactory from '@/core/analyzer/symbol/factory'; -import { CompileError, CompileErrorCode } from '@/core/errors'; +import { CompileError, CompileErrorCode, CompileWarning } from '@/core/errors'; import { BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; @@ -28,15 +28,18 @@ export default class EnumValidator implements ElementValidator { this.symbolFactory = symbolFactory; } - validate (): CompileError[] { - return [ - ...this.validateContext(), - ...this.validateName(this.declarationNode.name), - ...this.validateAlias(this.declarationNode.alias), - ...this.validateSettingList(this.declarationNode.attributeList), - ...this.registerElement(), - ...this.validateBody(this.declarationNode.body), - ]; + validate (): { errors: CompileError[], warnings: CompileWarning[] } { + return { + errors: [ + ...this.validateContext(), + ...this.validateName(this.declarationNode.name), + ...this.validateAlias(this.declarationNode.alias), + ...this.validateSettingList(this.declarationNode.attributeList), + ...this.registerElement(), + ...this.validateBody(this.declarationNode.body), + ], + warnings: [], + }; } private validateContext (): CompileError[] { @@ -173,7 +176,7 @@ export default class EnumValidator implements ElementValidator { } const _Validator = pickValidator(sub as ElementDeclarationNode & { type: SyntaxToken }); const validator = new _Validator(sub as ElementDeclarationNode & { type: SyntaxToken }, this.publicSymbolTable, this.symbolFactory); - return validator.validate(); + return validator.validate().errors; }); } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/indexes.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/indexes.ts index 14ee619f1..6aad64958 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/indexes.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/indexes.ts @@ -1,6 +1,6 @@ import { last, partition } from 'lodash-es'; import SymbolFactory from '@/core/analyzer/symbol/factory'; -import { CompileError, CompileErrorCode } from '@/core/errors'; +import { CompileError, CompileErrorCode, CompileWarning } from '@/core/errors'; import { BlockExpressionNode, CallExpressionNode, @@ -33,14 +33,17 @@ export default class IndexesValidator implements ElementValidator { this.symbolFactory = symbolFactory; } - validate (): CompileError[] { - return [ - ...this.validateContext(), - ...this.validateName(this.declarationNode.name), - ...this.validateAlias(this.declarationNode.alias), - ...this.validateSettingList(this.declarationNode.attributeList), - ...this.validateBody(this.declarationNode.body), - ]; + validate (): { errors: CompileError[], warnings: CompileWarning[] } { + return { + errors: [ + ...this.validateContext(), + ...this.validateName(this.declarationNode.name), + ...this.validateAlias(this.declarationNode.alias), + ...this.validateSettingList(this.declarationNode.attributeList), + ...this.validateBody(this.declarationNode.body), + ], + warnings: [], + }; } private validateContext (): CompileError[] { @@ -183,7 +186,7 @@ export default class IndexesValidator implements ElementValidator { } const _Validator = pickValidator(sub as ElementDeclarationNode & { type: SyntaxToken }); const validator = new _Validator(sub as ElementDeclarationNode & { type: SyntaxToken }, this.publicSymbolTable, this.symbolFactory); - return validator.validate(); + return validator.validate().errors; }); } } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/note.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/note.ts index c72080ad0..30e6bd0dd 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/note.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/note.ts @@ -1,6 +1,6 @@ import { partition } from 'lodash-es'; import SymbolFactory from '@/core/analyzer/symbol/factory'; -import { CompileError, CompileErrorCode } from '@/core/errors'; +import { CompileError, CompileErrorCode, CompileWarning } from '@/core/errors'; import { BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, ProgramNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; @@ -25,14 +25,17 @@ export default class NoteValidator implements ElementValidator { this.symbolFactory = symbolFactory; } - validate (): CompileError[] { - return [ - ...this.validateContext(), - ...this.validateName(this.declarationNode.name), - ...this.validateAlias(this.declarationNode.alias), - ...this.validateSettingList(this.declarationNode.attributeList), - ...this.validateBody(this.declarationNode.body), - ]; + validate (): { errors: CompileError[], warnings: CompileWarning[] } { + return { + errors: [ + ...this.validateContext(), + ...this.validateName(this.declarationNode.name), + ...this.validateAlias(this.declarationNode.alias), + ...this.validateSettingList(this.declarationNode.attributeList), + ...this.validateBody(this.declarationNode.body), + ], + warnings: [], + }; } private validateContext (): CompileError[] { @@ -145,7 +148,7 @@ export default class NoteValidator implements ElementValidator { } const _Validator = pickValidator(sub as ElementDeclarationNode & { type: SyntaxToken }); const validator = new _Validator(sub as ElementDeclarationNode & { type: SyntaxToken }, this.publicSymbolTable, this.symbolFactory); - return validator.validate(); + return validator.validate().errors; }); } } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/project.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/project.ts index da54246ee..2f1223f44 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/project.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/project.ts @@ -1,6 +1,6 @@ import { partition } from 'lodash-es'; import SymbolFactory from '@/core/analyzer/symbol/factory'; -import { CompileError, CompileErrorCode } from '@/core/errors'; +import { CompileError, CompileErrorCode, CompileWarning } from '@/core/errors'; import { BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; @@ -20,8 +20,11 @@ export default class ProjectValidator implements ElementValidator { this.symbolFactory = symbolFactory; } - validate (): CompileError[] { - return [...this.validateContext(), ...this.validateName(this.declarationNode.name), ...this.validateAlias(this.declarationNode.alias), ...this.validateSettingList(this.declarationNode.attributeList), ...this.validateBody(this.declarationNode.body)]; + validate (): { errors: CompileError[], warnings: CompileWarning[] } { + return { + errors: [...this.validateContext(), ...this.validateName(this.declarationNode.name), ...this.validateAlias(this.declarationNode.alias), ...this.validateSettingList(this.declarationNode.attributeList), ...this.validateBody(this.declarationNode.body)], + warnings: [], + }; } private validateContext (): CompileError[] { @@ -86,7 +89,7 @@ export default class ProjectValidator implements ElementValidator { } const _Validator = pickValidator(sub as ElementDeclarationNode & { type: SyntaxToken }); const validator = new _Validator(sub as ElementDeclarationNode & { type: SyntaxToken }, this.publicSymbolTable, this.symbolFactory); - return validator.validate(); + return validator.validate().errors; }); } } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/records.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/records.ts index 8d87b6a01..073616aa0 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/records.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/records.ts @@ -1,6 +1,6 @@ import { partition } from 'lodash-es'; import SymbolFactory from '@/core/analyzer/symbol/factory'; -import { CompileError, CompileErrorCode } from '@/core/errors'; +import { CompileError, CompileErrorCode, CompileWarning } from '@/core/errors'; import { BlockExpressionNode, CallExpressionNode, CommaExpressionNode, ElementDeclarationNode, EmptyNode, FunctionApplicationNode, FunctionExpressionNode, ListExpressionNode, ProgramNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; @@ -24,8 +24,11 @@ export default class RecordsValidator implements ElementValidator { this.symbolFactory = symbolFactory; } - validate (): CompileError[] { - return [...this.validateContext(), ...this.validateName(this.declarationNode.name), ...this.validateAlias(this.declarationNode.alias), ...this.validateSettingList(this.declarationNode.attributeList), ...this.validateBody(this.declarationNode.body)]; + validate (): { errors: CompileError[], warnings: CompileWarning[] } { + return { + errors: [...this.validateContext(), ...this.validateName(this.declarationNode.name), ...this.validateAlias(this.declarationNode.alias), ...this.validateSettingList(this.declarationNode.attributeList), ...this.validateBody(this.declarationNode.body)], + warnings: [], + }; } // Validate that Records can only appear top-level or inside a Table. @@ -258,7 +261,7 @@ export default class RecordsValidator implements ElementValidator { } const _Validator = pickValidator(sub as ElementDeclarationNode & { type: SyntaxToken }); const validator = new _Validator(sub as ElementDeclarationNode & { type: SyntaxToken }, this.publicSymbolTable, this.symbolFactory); - return validator.validate(); + return validator.validate().errors; }); } } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/ref.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/ref.ts index 537e7dd6f..2126132d7 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/ref.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/ref.ts @@ -1,7 +1,7 @@ import { partition, last } from 'lodash-es'; import { SyntaxToken, SyntaxTokenKind } from '@/core/lexer/tokens'; import SymbolFactory from '@/core/analyzer/symbol/factory'; -import { CompileError, CompileErrorCode } from '@/core/errors'; +import { CompileError, CompileErrorCode, CompileWarning } from '@/core/errors'; import { BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, IdentiferStreamNode, ListExpressionNode, ProgramNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; @@ -25,14 +25,17 @@ export default class RefValidator implements ElementValidator { this.symbolFactory = symbolFactory; } - validate (): CompileError[] { - return [ - ...this.validateContext(), - ...this.validateName(this.declarationNode.name), - ...this.validateAlias(this.declarationNode.alias), - ...this.validateSettingList(this.declarationNode.attributeList), - ...this.validateBody(this.declarationNode.body), - ]; + validate (): { errors: CompileError[], warnings: CompileWarning[] } { + return { + errors: [ + ...this.validateContext(), + ...this.validateName(this.declarationNode.name), + ...this.validateAlias(this.declarationNode.alias), + ...this.validateSettingList(this.declarationNode.attributeList), + ...this.validateBody(this.declarationNode.body), + ], + warnings: [], + }; } private validateContext (): CompileError[] { @@ -178,7 +181,7 @@ export default class RefValidator implements ElementValidator { } const _Validator = pickValidator(sub as ElementDeclarationNode & { type: SyntaxToken }); const validator = new _Validator(sub as ElementDeclarationNode & { type: SyntaxToken }, this.publicSymbolTable, this.symbolFactory); - return validator.validate(); + return validator.validate().errors; }); } } diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/table.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/table.ts index f4cb8b1f9..bb7e23e84 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/table.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/table.ts @@ -1,7 +1,7 @@ import { DEFAULT_SCHEMA_NAME } from '@/constants'; import { last, forIn, partition } from 'lodash-es'; import SymbolFactory from '@/core/analyzer/symbol/factory'; -import { CompileError, CompileErrorCode } from '@/core/errors'; +import { CompileError, CompileErrorCode, CompileWarning } from '@/core/errors'; import { ArrayNode, AttributeNode, @@ -58,15 +58,18 @@ export default class TableValidator implements ElementValidator { this.publicSymbolTable = publicSymbolTable; } - validate (): CompileError[] { - return [ - ...this.validateContext(), - ...this.validateName(this.declarationNode.name), - ...this.validateAlias(this.declarationNode.alias), - ...this.validateSettingList(this.declarationNode.attributeList), - ...this.registerElement(), - ...this.validateBody(this.declarationNode.body), - ]; + validate (): { errors: CompileError[], warnings: CompileWarning[] } { + return { + errors: [ + ...this.validateContext(), + ...this.validateName(this.declarationNode.name), + ...this.validateAlias(this.declarationNode.alias), + ...this.validateSettingList(this.declarationNode.attributeList), + ...this.registerElement(), + ...this.validateBody(this.declarationNode.body), + ], + warnings: [], + }; } private validateContext (): CompileError[] { @@ -438,7 +441,7 @@ export default class TableValidator implements ElementValidator { } const _Validator = pickValidator(sub as ElementDeclarationNode & { type: SyntaxToken }); const validator = new _Validator(sub as ElementDeclarationNode & { type: SyntaxToken }, this.publicSymbolTable, this.symbolFactory); - return validator.validate(); + return validator.validate().errors; }); const notes = subs.filter((sub) => sub.type?.value.toLowerCase() === 'note'); diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tableGroup.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tableGroup.ts index 8b26a6b95..3111d8263 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tableGroup.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tableGroup.ts @@ -1,5 +1,5 @@ import { forIn, partition } from 'lodash-es'; -import { CompileError, CompileErrorCode } from '@/core/errors'; +import { CompileError, CompileErrorCode, CompileWarning } from '@/core/errors'; import { isSimpleName, pickValidator } from '@/core/analyzer/validator/utils'; import { isValidColor, registerSchemaStack, aggregateSettingList } from '@/core/analyzer/validator/utils'; @@ -26,15 +26,18 @@ export default class TableGroupValidator implements ElementValidator { this.symbolFactory = symbolFactory; } - validate (): CompileError[] { - return [ - ...this.validateContext(), - ...this.validateName(this.declarationNode.name), - ...this.validateAlias(this.declarationNode.alias), - ...this.validateSettingList(this.declarationNode.attributeList), - ...this.registerElement(), - ...this.validateBody(this.declarationNode.body), - ]; + validate (): { errors: CompileError[], warnings: CompileWarning[] } { + return { + errors: [ + ...this.validateContext(), + ...this.validateName(this.declarationNode.name), + ...this.validateAlias(this.declarationNode.alias), + ...this.validateSettingList(this.declarationNode.attributeList), + ...this.registerElement(), + ...this.validateBody(this.declarationNode.body), + ], + warnings: [], + }; } private validateContext (): CompileError[] { @@ -197,7 +200,7 @@ export default class TableGroupValidator implements ElementValidator { } const _Validator = pickValidator(sub as ElementDeclarationNode & { type: SyntaxToken }); const validator = new _Validator(sub as ElementDeclarationNode & { type: SyntaxToken }, this.publicSymbolTable, this.symbolFactory); - return validator.validate(); + return validator.validate().errors; }); const notes = subs.filter((sub) => sub.type?.value.toLowerCase() === 'note'); diff --git a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tablePartial.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tablePartial.ts index 9ab60974b..feccff332 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tablePartial.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/tablePartial.ts @@ -1,6 +1,6 @@ import { partition, forIn, last } from 'lodash-es'; import SymbolFactory from '@/core/analyzer/symbol/factory'; -import { CompileError, CompileErrorCode } from '@/core/errors'; +import { CompileError, CompileErrorCode, CompileWarning } from '@/core/errors'; import { AttributeNode, BlockExpressionNode, @@ -54,15 +54,18 @@ export default class TablePartialValidator implements ElementValidator { this.publicSymbolTable = publicSymbolTable; } - validate (): CompileError[] { - return [ - ...this.validateContext(), - ...this.validateName(this.declarationNode.name), - ...this.validateAlias(this.declarationNode.alias), - ...this.validateSettingList(this.declarationNode.attributeList), - ...this.registerElement(), - ...this.validateBody(this.declarationNode.body), - ]; + validate (): { errors: CompileError[], warnings: CompileWarning[] } { + return { + errors: [ + ...this.validateContext(), + ...this.validateName(this.declarationNode.name), + ...this.validateAlias(this.declarationNode.alias), + ...this.validateSettingList(this.declarationNode.attributeList), + ...this.registerElement(), + ...this.validateBody(this.declarationNode.body), + ], + warnings: [], + }; } private validateContext (): CompileError[] { @@ -407,7 +410,7 @@ export default class TablePartialValidator implements ElementValidator { } const _Validator = pickValidator(sub as ElementDeclarationNode & { type: SyntaxToken }); const validator = new _Validator(sub as ElementDeclarationNode & { type: SyntaxToken }, this.publicSymbolTable, this.symbolFactory); - return validator.validate(); + return validator.validate().errors; }); const notes = subs.filter((sub) => sub.type?.value.toLowerCase() === ElementKind.Note); diff --git a/packages/dbml-parse/src/core/analyzer/validator/types.ts b/packages/dbml-parse/src/core/analyzer/validator/types.ts index c477d53b7..c6cd8c08d 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/types.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/types.ts @@ -1,5 +1,5 @@ -import { CompileError } from '@/core/errors'; +import { CompileError, CompileWarning } from '@/core/errors'; export interface ElementValidator { - validate(): CompileError[]; + validate(): { errors: CompileError[], warnings: CompileWarning[] }; } diff --git a/packages/dbml-parse/src/core/analyzer/validator/validator.ts b/packages/dbml-parse/src/core/analyzer/validator/validator.ts index 93c8e8816..4f5061c71 100644 --- a/packages/dbml-parse/src/core/analyzer/validator/validator.ts +++ b/packages/dbml-parse/src/core/analyzer/validator/validator.ts @@ -1,5 +1,5 @@ import Report from '@/core/report'; -import { CompileError, CompileErrorCode } from '@/core/errors'; +import { CompileError, CompileErrorCode, CompileWarning } from '@/core/errors'; import { ElementDeclarationNode, ProgramNode } from '@/core/parser/nodes'; import { SchemaSymbol } from '@/core/analyzer/symbol/symbols'; import SymbolFactory from '@/core/analyzer/symbol/factory'; @@ -29,6 +29,7 @@ export default class Validator { validate (): Report { const errors: CompileError[] = []; + const warnings: CompileWarning[] = []; this.ast.body.forEach((element) => { element.parent = this.ast; @@ -42,7 +43,9 @@ export default class Validator { this.publicSchemaSymbol.symbolTable, this.symbolFactory, ); - errors.push(...validatorObject.validate()); + const result = validatorObject.validate(); + errors.push(...result.errors); + warnings.push(...result.warnings); }); const projects = this.ast.body.filter((e) => getElementKind(e).unwrap_or(undefined) === ElementKind.Project); @@ -50,6 +53,6 @@ export default class Validator { projects.forEach((project) => errors.push(new CompileError(CompileErrorCode.PROJECT_REDEFINED, 'Only one project can exist', project))); } - return new Report(this.ast, errors); + return new Report(this.ast, errors, warnings); } } diff --git a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts index 941ae6989..cea9fe35b 100644 --- a/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts +++ b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts @@ -1,6 +1,6 @@ import { partition } from 'lodash-es'; import { destructureComplexVariable, extractReferee } from '@/core/analyzer/utils'; -import { CompileError } from '@/core/errors'; +import { CompileError, CompileErrorCode } from '@/core/errors'; import { BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, SyntaxNode } from '@/core/parser/nodes'; import { isWildcardExpression } from '@/core/parser/utils'; import { ElementInterpreter, InterpreterDatabase, DiagramView } from '@/core/interpreter/types'; @@ -22,8 +22,6 @@ export class DiagramViewInterpreter implements ElementInterpreter { tableGroups: null, schemas: null, }, - _explicitWildcards: new Set(), - _explicitlySet: new Set(), }; } @@ -31,11 +29,9 @@ export class DiagramViewInterpreter implements ElementInterpreter { const errors: CompileError[] = []; this.diagramView.token = getTokenPosition(this.declarationNode); - // Initialize diagramViews map if not exists - if (!this.env.diagramViews) { - this.env.diagramViews = new Map(); - } this.env.diagramViews.set(this.declarationNode, this.diagramView as DiagramView); + this.env.diagramViewWildcards.set(this.diagramView as DiagramView, new Set()); + this.env.diagramViewExplicitlySet.set(this.diagramView as DiagramView, new Set()); // Interpret name if (this.declarationNode.name) { @@ -67,8 +63,8 @@ export class DiagramViewInterpreter implements ElementInterpreter { const first = body.body[0]; if (first instanceof FunctionApplicationNode && isWildcardExpression(first.callee)) { this.diagramView.visibleEntities = { tables: [], stickyNotes: [], tableGroups: [], schemas: [] }; - this.diagramView._explicitWildcards = new Set(['tables', 'stickyNotes', 'tableGroups', 'schemas']); - this.diagramView._explicitlySet = new Set(['tables', 'stickyNotes', 'tableGroups', 'schemas']); + this.env.diagramViewWildcards.set(this.diagramView as DiagramView, new Set(['tables', 'stickyNotes', 'tableGroups', 'schemas'])); + this.env.diagramViewExplicitlySet.set(this.diagramView as DiagramView, new Set(['tables', 'stickyNotes', 'tableGroups', 'schemas'])); return []; } } @@ -99,10 +95,11 @@ export class DiagramViewInterpreter implements ElementInterpreter { } // Store which dims were explicitly declared (normalize block type names to FilterConfig keys) - if (explicitlySet.has('tables')) this.diagramView._explicitlySet!.add('tables'); - if (explicitlySet.has('tablegroups')) this.diagramView._explicitlySet!.add('tableGroups'); - if (explicitlySet.has('schemas')) this.diagramView._explicitlySet!.add('schemas'); - if (explicitlySet.has('notes')) this.diagramView._explicitlySet!.add('stickyNotes'); + const envExplicitlySet = this.env.diagramViewExplicitlySet.get(this.diagramView as DiagramView)!; + if (explicitlySet.has('tables')) envExplicitlySet.add('tables'); + if (explicitlySet.has('tablegroups')) envExplicitlySet.add('tableGroups'); + if (explicitlySet.has('schemas')) envExplicitlySet.add('schemas'); + if (explicitlySet.has('notes')) envExplicitlySet.add('stickyNotes'); return []; } @@ -117,22 +114,23 @@ export class DiagramViewInterpreter implements ElementInterpreter { if (hasWildcard) { // Show all for this entity type + const envWildcards = this.env.diagramViewWildcards.get(this.diagramView as DiagramView)!; switch (blockType) { case 'tables': this.diagramView.visibleEntities!.tables = []; - this.diagramView._explicitWildcards!.add('tables'); + envWildcards.add('tables'); break; case 'notes': this.diagramView.visibleEntities!.stickyNotes = []; - this.diagramView._explicitWildcards!.add('stickyNotes'); + envWildcards.add('stickyNotes'); break; case 'tablegroups': this.diagramView.visibleEntities!.tableGroups = []; - this.diagramView._explicitWildcards!.add('tableGroups'); + envWildcards.add('tableGroups'); break; case 'schemas': this.diagramView.visibleEntities!.schemas = []; - this.diagramView._explicitWildcards!.add('schemas'); + envWildcards.add('schemas'); break; } return; diff --git a/packages/dbml-parse/src/core/interpreter/interpreter.ts b/packages/dbml-parse/src/core/interpreter/interpreter.ts index a4c9f1e43..e6b37d9c4 100644 --- a/packages/dbml-parse/src/core/interpreter/interpreter.ts +++ b/packages/dbml-parse/src/core/interpreter/interpreter.ts @@ -35,20 +35,16 @@ function processColumnInDb (table: T): T { * to the concrete list of table group names. * * Only expands when: - * 1. The user wrote `TableGroups { * }` (tracked via _explicitWildcards) + * 1. The user wrote `TableGroups { * }` (tracked via diagramViewWildcards) * 2. No other Trinity dim (Tables, Schemas) is explicitly set — * i.e. tableGroups is the only Trinity dim declared. * When other Trinity dims are also declared, [] keeps its "show all" meaning. - * - * Also cleans up internal markers (_explicitWildcards, _explicitlySet) before output. */ function expandDiagramViewWildcards (env: InterpreterDatabase): void { - if (!env.diagramViews) return; - for (const view of env.diagramViews.values()) { const ve = view.visibleEntities; - const wildcards = view._explicitWildcards; - const explicitlySet = view._explicitlySet; + const wildcards = env.diagramViewWildcards.get(view); + const explicitlySet = env.diagramViewExplicitlySet.get(view); if (!wildcards || !explicitlySet) continue; if (wildcards.has('tableGroups') && ve.tableGroups && ve.tableGroups.length === 0) { @@ -59,10 +55,6 @@ function expandDiagramViewWildcards (env: InterpreterDatabase): void { })); } } - - // Clean up internal markers before output - delete view._explicitWildcards; - delete view._explicitlySet; } } @@ -100,7 +92,7 @@ function convertEnvToDb (env: InterpreterDatabase): Database { project: Array.from(env.project.values())[0] || {}, tablePartials: Array.from(env.tablePartials.values()).map(processColumnInDb), records, - diagramViews: env.diagramViews ? Array.from(env.diagramViews.values()) : [], + diagramViews: Array.from(env.diagramViews.values()), }; } @@ -128,6 +120,8 @@ export default class Interpreter { cachedMergedTables: new Map(), source: ast.source, diagramViews: new Map(), + diagramViewWildcards: new Map(), + diagramViewExplicitlySet: new Map(), }; } @@ -152,7 +146,6 @@ export default class Interpreter { case ElementKind.DiagramView: return (new DiagramViewInterpreter(element, this.env)).interpret(); case ElementKind.Records: - // Defer records interpretation - collect for later this.env.recordsElements.push(element); return []; default: @@ -161,6 +154,7 @@ export default class Interpreter { }); const warnings: CompileWarning[] = []; + if (this.env.recordsElements.length) { // Second pass: interpret all records elements grouped by table // Now that all tables, enums, etc. are interpreted, we can validate records properly diff --git a/packages/dbml-parse/src/core/interpreter/types.ts b/packages/dbml-parse/src/core/interpreter/types.ts index 01d6db7d5..6874d0aee 100644 --- a/packages/dbml-parse/src/core/interpreter/types.ts +++ b/packages/dbml-parse/src/core/interpreter/types.ts @@ -29,10 +29,6 @@ export interface DiagramView { schemaName: string | null; visibleEntities: FilterConfig; token: TokenPosition; - /** Internal: tracks which dims used explicit wildcard (*). Stripped before output. */ - _explicitWildcards?: Set; - /** Internal: tracks which dims were explicitly declared in the DBML. Stripped before output. */ - _explicitlySet?: Set; } export interface InterpreterDatabase { @@ -52,7 +48,9 @@ export interface InterpreterDatabase { recordsElements: ElementDeclarationNode[]; cachedMergedTables: Map; // map Table to Table that has been merged with table partials source: string; - diagramViews?: Map; + diagramViews: Map; + diagramViewWildcards: Map>; + diagramViewExplicitlySet: Map>; } // Record value type diff --git a/packages/dbml-parse/src/services/suggestions/provider.ts b/packages/dbml-parse/src/services/suggestions/provider.ts index d1896d29a..a9d5e0ad2 100644 --- a/packages/dbml-parse/src/services/suggestions/provider.ts +++ b/packages/dbml-parse/src/services/suggestions/provider.ts @@ -48,6 +48,7 @@ import { import { getOffsetFromMonacoPosition } from '@/services/utils'; import { isComment } from '@/core/lexer/utils'; import { ElementKind, SettingName } from '@/core/analyzer/types'; +import { DEFAULT_SCHEMA_NAME } from '@/constants'; export default class DBMLCompletionItemProvider implements CompletionItemProvider { private compiler: Compiler; @@ -580,7 +581,7 @@ function suggestInSubField ( if ( element instanceof ElementDeclarationNode && element.parent instanceof ElementDeclarationNode - && element.parent.type?.value.toLowerCase() === 'diagramview' + && getElementKind(element.parent).unwrap_or(undefined) === ElementKind.DiagramView ) { return suggestInDiagramViewSubBlock(compiler, offset); } @@ -847,75 +848,29 @@ function suggestInDiagramViewSubBlock (compiler: Compiler, offset: number): Comp }; switch (blockType) { - case 'tables': - return { - suggestions: [ - wildcard, - ...addQuoteToSuggestionIfNeeded({ - suggestions: [...compiler.parse.publicSymbolTable().entries()].flatMap(([index]) => { - const res = destructureIndex(index).unwrap_or(undefined); - if (res === undefined) return []; - const { kind, name } = res; - if (kind !== SymbolKind.Table && kind !== SymbolKind.Schema) return []; - return { - label: name, - insertText: name, - insertTextRules: CompletionItemInsertTextRule.KeepWhitespace, - kind: pickCompletionItemKind(kind), - range: undefined as any, - }; - }), - }).suggestions, - ], - }; - case 'tablegroups': - return { - suggestions: [ - wildcard, - ...addQuoteToSuggestionIfNeeded({ - suggestions: [...compiler.parse.publicSymbolTable().entries()].flatMap(([index]) => { - const res = destructureIndex(index).unwrap_or(undefined); - if (res === undefined) return []; - const { kind, name } = res; - if (kind !== SymbolKind.TableGroup) return []; - return { - label: name, - insertText: name, - insertTextRules: CompletionItemInsertTextRule.KeepWhitespace, - kind: pickCompletionItemKind(kind), - range: undefined as any, - }; - }), - }).suggestions, - ], - }; + case 'tables': { + const namesInScope = suggestNamesInScope(compiler, offset, compiler.parse.ast(), [SymbolKind.Table, SymbolKind.Schema]); + return { suggestions: [wildcard, ...namesInScope.suggestions] }; + } + case 'tablegroups': { + const namesInScope = suggestNamesInScope(compiler, offset, compiler.parse.ast(), [SymbolKind.TableGroup]); + return { suggestions: [wildcard, ...namesInScope.suggestions] }; + } case 'schemas': { - const schemaNames = new Set(); - const ast = compiler.parse.ast(); - for (const el of ast?.body || []) { - if (el instanceof ElementDeclarationNode && el.name instanceof InfixExpressionNode && el.name.op?.value === '.') { - const fragments = destructureMemberAccessExpression(el.name).unwrap_or([]); - if (fragments.length >= 2) { - const schemaName = extractVariableFromExpression(fragments[0]).unwrap_or(''); - if (schemaName) schemaNames.add(schemaName); - } - } - } - return { - suggestions: [ - wildcard, - ...[...schemaNames].map((name) => ({ - label: name, - insertText: name, - insertTextRules: CompletionItemInsertTextRule.KeepWhitespace, - kind: CompletionItemKind.Module, - range: undefined as any, - })), - ], + const defaultSchema = { + label: DEFAULT_SCHEMA_NAME, + insertText: DEFAULT_SCHEMA_NAME, + insertTextRules: CompletionItemInsertTextRule.KeepWhitespace, + kind: CompletionItemKind.Module, + range: undefined as any, }; + const namesInScope = suggestNamesInScope(compiler, offset, compiler.parse.ast(), [SymbolKind.Schema]); + return { suggestions: [wildcard, defaultSchema, ...namesInScope.suggestions] }; + } + case 'notes': { + const namesInScope = suggestNamesInScope(compiler, offset, compiler.parse.ast(), [SymbolKind.Note]); + return { suggestions: [wildcard, ...namesInScope.suggestions] }; } - case 'notes': - return { suggestions: [wildcard] }; default: return noSuggestions(); } From 88d165f28c7890b62ca074984962df05cb363e2b Mon Sep 17 00:00:00 2001 From: NQPhuc <11730168+NQPhuc@users.noreply.github.com> Date: Tue, 31 Mar 2026 19:26:38 +0700 Subject: [PATCH 28/31] fix: add sorting and refactor syncDiagramView --- .../queries/transform/applyTextEdits.ts | 4 +- .../queries/transform/syncDiagramView.ts | 54 +++++++++---------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/packages/dbml-parse/src/compiler/queries/transform/applyTextEdits.ts b/packages/dbml-parse/src/compiler/queries/transform/applyTextEdits.ts index 357cd07b0..6540a3773 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/applyTextEdits.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/applyTextEdits.ts @@ -11,8 +11,8 @@ export interface TextEdit { * @param edits - Array of text edits to apply * @returns The modified source string with all edits applied */ -export function applyTextEdits (source: string, edits: TextEdit[]): string { - const sortedEdits = [...edits].sort((a, b) => b.start - a.start); +export function applyTextEdits (source: string, edits: TextEdit[], sorted = false): string { + const sortedEdits = sorted ? edits : [...edits].sort((a, b) => b.start - a.start); let result = source; for (const { start, end, newText } of sortedEdits) { diff --git a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts index 3b9fc7db1..53311e5a8 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts @@ -140,69 +140,64 @@ export function syncDiagramView ( operations: DiagramViewSyncOperation[], blocks?: DiagramViewBlock[], ): { newDbml: string; edits: TextEdit[] } { - let currentBlocks = blocks ?? findDiagramViewBlocks(dbml); - let currentDbml = dbml; + const originalBlocks = blocks ?? findDiagramViewBlocks(dbml); const allEdits: TextEdit[] = []; for (const op of operations) { - const { dbml: newDbml, edits } = applyOperation(currentDbml, op, currentBlocks); + const edits = applyOperation(dbml, op, originalBlocks); allEdits.push(...edits); - currentDbml = newDbml; - // Re-parse blocks after each operation since positions shifted - if (edits.length > 0) { - currentBlocks = findDiagramViewBlocks(currentDbml); - } } - return { newDbml: currentDbml, edits: allEdits }; + // Sort edits descending by start position for tail-first application + allEdits.sort((a, b) => b.start - a.start); + const newDbml = applyTextEdits(dbml, allEdits, true); + return { newDbml, edits: allEdits }; } function applyOperation ( dbml: string, operation: DiagramViewSyncOperation, blocks: DiagramViewBlock[], -): { dbml: string; edits: TextEdit[] } { +): TextEdit[] { switch (operation.operation) { case 'create': - return applyCreate(dbml, operation, blocks); + return computeCreateEdit(dbml, operation, blocks); case 'update': - return applyUpdate(dbml, operation, blocks); + return computeUpdateEdit(dbml, operation, blocks); case 'delete': - return applyDelete(dbml, operation, blocks); + return computeDeleteEdit(dbml, operation, blocks); default: - return { dbml, edits: [] }; + return []; } } -function applyCreate ( +function computeCreateEdit ( dbml: string, operation: DiagramViewSyncOperation, blocks: DiagramViewBlock[], -): { dbml: string; edits: TextEdit[] } { +): TextEdit[] { // If a block with this name already exists, treat as update to avoid duplicate blocks const existing = blocks.find((b) => b.name === operation.name); if (existing) { - return applyUpdate(dbml, operation, blocks); + return computeUpdateEdit(dbml, operation, blocks); } const newBlock = generateDiagramViewBlock(operation.name, operation.visibleEntities); const appendText = '\n\n' + newBlock + '\n'; - const edit: TextEdit = { + return [{ start: dbml.length, end: dbml.length, newText: appendText, - }; - - return { dbml: dbml + appendText, edits: [edit] }; + }]; } -function applyUpdate ( +function computeUpdateEdit ( dbml: string, operation: DiagramViewSyncOperation, blocks: DiagramViewBlock[], -): { dbml: string; edits: TextEdit[] } { +): TextEdit[] { const block = blocks.find((b) => b.name === operation.name); - if (!block) return { dbml, edits: [] }; + if (!block) return []; const edits: TextEdit[] = []; @@ -219,16 +214,16 @@ function applyUpdate ( }); } - return { dbml: applyTextEdits(dbml, edits), edits }; + return edits; } -function applyDelete ( +function computeDeleteEdit ( dbml: string, operation: DiagramViewSyncOperation, blocks: DiagramViewBlock[], -): { dbml: string; edits: TextEdit[] } { +): TextEdit[] { const block = blocks.find((b) => b.name === operation.name); - if (!block) return { dbml, edits: [] }; + if (!block) return []; // Expand range to include surrounding whitespace/newlines let start = block.startIndex; @@ -260,6 +255,5 @@ function applyDelete ( if (end < dbml.length && dbml[end] === '\r') end++; if (end < dbml.length && dbml[end] === '\n') end++; - const edit: TextEdit = { start, end, newText: '' }; - return { dbml: applyTextEdits(dbml, [edit]), edits: [edit] }; + return [{ start, end, newText: '' }]; } From 283f3bcf7373ecab1e808e8a8035ce906ee6a52e Mon Sep 17 00:00:00 2001 From: NQPhuc <11730168+NQPhuc@users.noreply.github.com> Date: Tue, 31 Mar 2026 19:47:26 +0700 Subject: [PATCH 29/31] v7.0.0-alpha.1 --- dbml-playground/package.json | 4 ++-- lerna.json | 2 +- packages/dbml-cli/package.json | 4 ++-- packages/dbml-core/package.json | 4 ++-- packages/dbml-parse/package.json | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dbml-playground/package.json b/dbml-playground/package.json index feb006a20..938c9ef2e 100644 --- a/dbml-playground/package.json +++ b/dbml-playground/package.json @@ -1,6 +1,6 @@ { "name": "@dbml/playground", - "version": "7.0.0-alpha.0", + "version": "7.0.0-alpha.1", "description": "Interactive playground for debugging and visualizing the DBML parser pipeline", "author": "Holistics ", "license": "Apache-2.0", @@ -25,7 +25,7 @@ "format": "prettier --write src/" }, "dependencies": { - "@dbml/parse": "^7.0.0-alpha.0", + "@dbml/parse": "^7.0.0-alpha.1", "lodash-es": "^4.17.21", "monaco-editor": "^0.52.2", "monaco-vim": "^0.4.2", diff --git a/lerna.json b/lerna.json index 70e8e5ed5..b6b0b7435 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "7.0.0-alpha.0", + "version": "7.0.0-alpha.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 9cbe64b0c..c34167c3e 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": "7.0.0-alpha.0", + "version": "7.0.0-alpha.1", "description": "", "main": "lib/index.js", "license": "Apache-2.0", @@ -33,7 +33,7 @@ "dependencies": { "@babel/cli": "^7.21.0", "@dbml/connector": "^7.0.0-alpha.0", - "@dbml/core": "^7.0.0-alpha.0", + "@dbml/core": "^7.0.0-alpha.1", "bluebird": "^3.5.5", "chalk": "^2.4.2", "commander": "^2.20.0", diff --git a/packages/dbml-core/package.json b/packages/dbml-core/package.json index 91c9f8712..423183d21 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": "7.0.0-alpha.0", + "version": "7.0.0-alpha.1", "description": "> TODO: description", "author": "Holistics ", "license": "Apache-2.0", @@ -46,7 +46,7 @@ "lint:fix": "eslint --fix ." }, "dependencies": { - "@dbml/parse": "^7.0.0-alpha.0", + "@dbml/parse": "^7.0.0-alpha.1", "antlr4": "^4.13.1", "lodash": "^4.17.15", "lodash-es": "^4.17.15", diff --git a/packages/dbml-parse/package.json b/packages/dbml-parse/package.json index 1bf3b8248..38fdd0814 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": "7.0.0-alpha.0", + "version": "7.0.0-alpha.1", "description": "DBML parser v2", "author": "Holistics ", "license": "Apache-2.0", From 052881385afc43cbb6a954247805a75a800258d6 Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Wed, 1 Apr 2026 10:47:24 +0700 Subject: [PATCH 30/31] Clean up duplicated code --- .../queries/transform/syncDiagramView.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts index 3b9fc7db1..bdd0bbe06 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts @@ -1,9 +1,11 @@ -import { applyTextEdits, TextEdit } from './applyTextEdits'; import Lexer from '@/core/lexer/lexer'; import Parser from '@/core/parser/parser'; +import { ElementKind } from '@/core/analyzer/types'; +import { DEFAULT_SCHEMA_NAME } from '@/constants'; import { SyntaxNodeIdGenerator } from '@/core/parser/nodes'; import { destructureComplexVariable } from '@/core/analyzer/utils'; -import { DEFAULT_SCHEMA_NAME } from '@/constants'; +import { applyTextEdits, TextEdit } from './applyTextEdits'; +import { addDoubleQuoteIfNeeded } from '../utils'; export interface DiagramViewSyncOperation { operation: 'create' | 'update' | 'delete'; @@ -35,7 +37,7 @@ export function findDiagramViewBlocks (source: string): DiagramViewBlock[] { const program = ast.getValue().ast; for (const element of program.body) { - if (element.type?.value === 'DiagramView') { + if (element.type?.value === ElementKind.DiagramView) { const fragments = element.name ? destructureComplexVariable(element.name).unwrap_or([]) : []; @@ -56,17 +58,11 @@ function needsQuoting (name: string): boolean { return !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name); } -/** Wraps name in double quotes and escapes internal double quotes if needed. */ -function quoteName (name: string): string { - if (!needsQuoting(name)) return name; - return `"${name.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`; -} - function generateDiagramViewBlock ( name: string, visibleEntities: DiagramViewSyncOperation['visibleEntities'], ): string { - const lines: string[] = [`DiagramView ${quoteName(name)} {`]; + const lines: string[] = [`DiagramView ${addDoubleQuoteIfNeeded(name)} {`]; // Tables if (visibleEntities?.tables !== undefined) { From b4e4c5539366a0c86c8ce62521bccaf24834065c Mon Sep 17 00:00:00 2001 From: huyphung1602 Date: Wed, 1 Apr 2026 10:57:45 +0700 Subject: [PATCH 31/31] Remove unused declaration --- AGENTS.md | 2 +- CLAUDE.md | 2 +- .../src/compiler/queries/transform/syncDiagramView.ts | 5 ----- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ca6662a3d..4e3299fb7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **dbml** (7344 symbols, 18708 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **dbml** (7344 symbols, 18714 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. diff --git a/CLAUDE.md b/CLAUDE.md index ca6662a3d..4e3299fb7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # GitNexus — Code Intelligence -This project is indexed by GitNexus as **dbml** (7344 symbols, 18708 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. +This project is indexed by GitNexus as **dbml** (7344 symbols, 18714 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first. diff --git a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts index 6014455a3..e4524a6d8 100644 --- a/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts +++ b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts @@ -53,11 +53,6 @@ export function findDiagramViewBlocks (source: string): DiagramViewBlock[] { return blocks; } -/** Returns true if the name requires double-quote wrapping in DBML. */ -function needsQuoting (name: string): boolean { - return !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name); -} - function generateDiagramViewBlock ( name: string, visibleEntities: DiagramViewSyncOperation['visibleEntities'],