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..4e3299fb7 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,101 @@ + +# GitNexus — Code Intelligence + +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. + +## 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` | + + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..4e3299fb7 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,101 @@ + +# GitNexus — Code Intelligence + +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. + +## 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` | + + diff --git a/dbml-playground/package.json b/dbml-playground/package.json index 17ae9e275..938c9ef2e 100644 --- a/dbml-playground/package.json +++ b/dbml-playground/package.json @@ -1,6 +1,6 @@ { "name": "@dbml/playground", - "version": "6.5.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": "^6.5.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 00d18abcc..b6b0b7435 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "6.5.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 3670335ea..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": "6.5.0", + "version": "7.0.0-alpha.1", "description": "", "main": "lib/index.js", "license": "Apache-2.0", @@ -32,8 +32,8 @@ ], "dependencies": { "@babel/cli": "^7.21.0", - "@dbml/connector": "^6.5.0", - "@dbml/core": "^6.5.0", + "@dbml/connector": "^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-connector/package.json b/packages/dbml-connector/package.json index f9e03e339..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.5.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 02b79b596..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": "6.5.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": "^6.5.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-core/src/index.ts b/packages/dbml-core/src/index.ts index 46bdae035..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,4 +40,15 @@ export { tryExtractEnum, addDoubleQuoteIfNeeded, formatRecordValue, + // Monaco editor syntax highlighting + dbmlMonarchTokensProvider, +} from '@dbml/parse'; + +// 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 515349b82..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, @@ -37,3 +41,4 @@ export { addDoubleQuoteIfNeeded, formatRecordValue, } 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/__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__/examples/interpreter/interpreter.test.ts b/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts index ea03b464b..5aac3639a 100644 --- a/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts +++ b/packages/dbml-parse/__tests__/examples/interpreter/interpreter.test.ts @@ -985,6 +985,279 @@ 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([]); + }); + + 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('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('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/__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/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/__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/__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/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/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/__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 e8430ee44..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": 23, + "id": 31, "kind": "", "startPos": { "offset": 0, @@ -18,7 +18,7 @@ "end": 69, "body": [ { - "id": 22, + "id": 30, "kind": "", "startPos": { "offset": 0, @@ -157,7 +157,7 @@ } }, "body": { - "id": 21, + "id": 29, "kind": "", "startPos": { "offset": 20, @@ -218,7 +218,7 @@ }, "body": [ { - "id": 6, + "id": 20, "kind": "", "startPos": { "offset": 27, @@ -227,16 +227,16 @@ }, "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, - "kind": "", + "id": 3, + "kind": "", "startPos": { "offset": 27, "line": 1, @@ -244,338 +244,16 @@ }, "fullStart": 23, "endPos": { - "offset": 31, + "offset": 28, "line": 1, - "column": 8 + "column": 5 }, - "fullEnd": 31, + "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": [ - { - "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 - } - ], - "isInvalid": false, - "start": 29, - "end": 30 - }, - "leftExpression": { - "id": 3, - "kind": "", + "end": 28, + "expression": { + "id": 2, + "kind": "", "startPos": { "offset": 27, "line": 1, @@ -590,225 +268,254 @@ "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 } - }, - "rightExpression": { + } + }, + "args": [ + { "id": 4, - "kind": "", + "kind": "", "startPos": { - "offset": 31, + "offset": 29, "line": 1, - "column": 8 + "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 + } + }, + { + "id": 5, + "kind": "", + "startPos": { + "offset": 30, + "line": 1, + "column": 7 }, - "fullStart": 31, + "fullStart": 30, "endPos": { "offset": 31, "line": 1, "column": 8 }, - "fullEnd": 31, - "start": 31, - "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": 32, + "start": 30, + "end": 31, + "token": { + "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 + } }, - "fullEnd": 55, - "start": 49, - "end": 53, - "callee": { - "id": 9, - "kind": "", + { + "id": 19, + "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": { @@ -873,51 +580,474 @@ "column": 2 }, "endPos": { - "offset": 48, - "line": 2, - "column": 3 + "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": 14, + "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": 7, + "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": 6, + "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": 13, + "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": 11, + "kind": "", + "startPos": { + "offset": 36, + "line": 1, + "column": 13 + }, + "fullStart": 36, + "endPos": { + "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": 9, + "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": 8, + "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": 10, + "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": 12, + "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": 18, + "kind": "", "startPos": { "offset": 50, "line": 2, @@ -925,16 +1055,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": 16, + "kind": "", "startPos": { "offset": 50, "line": 2, @@ -949,119 +1079,136 @@ "fullEnd": 51, "start": 50, "end": 51, - "literal": { - "kind": "", + "expression": { + "id": 15, + "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": 17, + "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": 28, "kind": "", "startPos": { "offset": 59, @@ -1078,7 +1225,7 @@ "start": 59, "end": 64, "callee": { - "id": 19, + "id": 27, "kind": "", "startPos": { "offset": 59, @@ -1095,7 +1242,7 @@ "start": 59, "end": 64, "callee": { - "id": 17, + "id": 25, "kind": "", "startPos": { "offset": 59, @@ -1133,7 +1280,7 @@ "end": 61 }, "leftExpression": { - "id": 14, + "id": 22, "kind": "", "startPos": { "offset": 59, @@ -1150,7 +1297,7 @@ "start": 59, "end": 60, "expression": { - "id": 13, + "id": 21, "kind": "", "startPos": { "offset": 59, @@ -1275,7 +1422,7 @@ } }, "rightExpression": { - "id": 16, + "id": 24, "kind": "", "startPos": { "offset": 61, @@ -1292,7 +1439,7 @@ "start": 61, "end": 62, "expression": { - "id": 15, + "id": 23, "kind": "", "startPos": { "offset": 61, @@ -1333,7 +1480,7 @@ } }, "argumentList": { - "id": 18, + "id": 26, "kind": "", "startPos": { "offset": 62, @@ -1490,53 +1637,49 @@ }, "errors": [ { - "code": 1008, - "diagnostic": "Unexpected '*' in an expression", + "code": 1007, + "diagnostic": "Expect a following space", "nodeOrToken": { - "kind": "", + "id": 4, + "kind": "", "startPos": { - "offset": 30, + "offset": 29, "line": 1, - "column": 7 + "column": 6 }, + "fullStart": 29, "endPos": { - "offset": 31, + "offset": 30, "line": 1, - "column": 8 + "column": 7 }, - "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 + "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": 30, - "end": 31, + "start": 29, + "end": 30, "name": "CompileError" } ] 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..0d3928d67 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,276 @@ } ], "leadingInvalid": [], - "trailingInvalid": [ - { - "kind": "", + "trailingInvalid": [], + "isInvalid": false, + "start": 16, + "end": 17 + }, + "body": [ + { + "id": 6, + "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": 2, + "kind": "", "startPos": { "offset": 23, "line": 1, "column": 4 }, + "fullStart": 19, "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 + "fullEnd": 24, + "start": 23, + "end": 24, + "token": { + "kind": "", + "startPos": { + "offset": 23, + "line": 1, + "column": 4 }, - { - "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": 24, + "line": 1, + "column": 5 }, - { - "kind": "", - "startPos": { - "offset": 21, - "line": 1, - "column": 2 + "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 }, - "endPos": { - "offset": 22, - "line": 1, - "column": 3 + { + "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 }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 21, - "end": 22 + { + "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 + } + }, + "args": [ + { + "id": 3, + "kind": "", + "startPos": { + "offset": 24, + "line": 1, + "column": 5 }, - { - "kind": "", + "fullStart": 24, + "endPos": { + "offset": 25, + "line": 1, + "column": 6 + }, + "fullEnd": 25, + "start": 24, + "end": 25, + "token": { + "kind": "", "startPos": { - "offset": 22, + "offset": 24, "line": 1, - "column": 3 + "column": 5 }, "endPos": { - "offset": 23, + "offset": 25, "line": 1, - "column": 4 + "column": 6 }, - "value": " ", + "value": "*", "leadingTrivia": [], "trailingTrivia": [], "leadingInvalid": [], "trailingInvalid": [], "isInvalid": false, - "start": 22, - "end": 23 + "start": 24, + "end": 25 } - ], - "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": "", + { + "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": 15, "kind": "", "startPos": { "offset": 38, @@ -452,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": { @@ -503,7 +557,7 @@ "end": 41 }, "leftExpression": { - "id": 5, + "id": 8, "kind": "", "startPos": { "offset": 38, @@ -520,7 +574,7 @@ "start": 38, "end": 39, "expression": { - "id": 4, + "id": 7, "kind": "", "startPos": { "offset": 38, @@ -773,7 +827,7 @@ }, "rightExpression": { "id": 10, - "kind": "", + "kind": "", "startPos": { "offset": 42, "line": 3, @@ -781,15 +835,96 @@ }, "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": 42, + "line": 3, + "column": 8 + }, + "fullStart": 42, + "endPos": { + "offset": 43, + "line": 3, + "column": 9 + }, + "fullEnd": 44, + "start": 42, + "end": 43, + "literal": { + "kind": "", + "startPos": { + "offset": 42, + "line": 3, + "column": 8 + }, + "endPos": { + "offset": 43, + "line": 3, + "column": 9 + }, + "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 + } + } + } + }, + "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, @@ -830,89 +965,28 @@ "isInvalid": false, "start": 44, "end": 45 + } + }, + { + "id": 14, + "kind": "", + "startPos": { + "offset": 46, + "line": 3, + "column": 12 }, - "leftExpression": { - "id": 7, - "kind": "", - "startPos": { - "offset": 42, - "line": 3, - "column": 8 - }, - "fullStart": 42, - "endPos": { - "offset": 43, - "line": 3, - "column": 9 - }, - "fullEnd": 44, - "start": 42, - "end": 43, - "expression": { - "id": 6, - "kind": "", - "startPos": { - "offset": 42, - "line": 3, - "column": 8 - }, - "fullStart": 42, - "endPos": { - "offset": 43, - "line": 3, - "column": 9 - }, - "fullEnd": 44, - "start": 42, - "end": 43, - "literal": { - "kind": "", - "startPos": { - "offset": 42, - "line": 3, - "column": 8 - }, - "endPos": { - "offset": 43, - "line": 3, - "column": 9 - }, - "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 - } - } + "fullStart": 46, + "endPos": { + "offset": 47, + "line": 3, + "column": 13 }, - "rightExpression": { - "id": 9, - "kind": "", + "fullEnd": 49, + "start": 46, + "end": 47, + "expression": { + "id": 13, + "kind": "", "startPos": { "offset": 46, "line": 3, @@ -927,74 +1001,55 @@ "fullEnd": 49, "start": 46, "end": 47, - "expression": { - "id": 8, - "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": 21, + "id": 24, "kind": "", "startPos": { "offset": 53, @@ -1011,7 +1066,7 @@ "start": 53, "end": 62, "callee": { - "id": 20, + "id": 23, "kind": "", "startPos": { "offset": 53, @@ -1071,7 +1126,7 @@ "end": 60 }, "leftExpression": { - "id": 17, + "id": 20, "kind": "", "startPos": { "offset": 53, @@ -1131,7 +1186,7 @@ "end": 56 }, "leftExpression": { - "id": 14, + "id": 17, "kind": "", "startPos": { "offset": 53, @@ -1148,7 +1203,7 @@ "start": 53, "end": 54, "expression": { - "id": 13, + "id": 16, "kind": "", "startPos": { "offset": 53, @@ -1295,7 +1350,7 @@ } }, "rightExpression": { - "id": 16, + "id": 19, "kind": "", "startPos": { "offset": 57, @@ -1312,7 +1367,7 @@ "start": 57, "end": 58, "expression": { - "id": 15, + "id": 18, "kind": "", "startPos": { "offset": 57, @@ -1375,7 +1430,7 @@ } }, "rightExpression": { - "id": 19, + "id": 22, "kind": "", "startPos": { "offset": 61, @@ -1392,7 +1447,7 @@ "start": 61, "end": 62, "expression": { - "id": 18, + "id": 21, "kind": "", "startPos": { "offset": 61, @@ -1457,7 +1512,7 @@ "args": [] }, { - "id": 31, + "id": 34, "kind": "", "startPos": { "offset": 68, @@ -1474,7 +1529,7 @@ "start": 68, "end": 78, "callee": { - "id": 30, + "id": 33, "kind": "", "startPos": { "offset": 68, @@ -1534,7 +1589,7 @@ "end": 76 }, "leftExpression": { - "id": 27, + "id": 30, "kind": "", "startPos": { "offset": 68, @@ -1594,7 +1649,7 @@ "end": 71 }, "leftExpression": { - "id": 23, + "id": 26, "kind": "", "startPos": { "offset": 68, @@ -1611,7 +1666,7 @@ "start": 68, "end": 69, "expression": { - "id": 22, + "id": 25, "kind": "", "startPos": { "offset": 68, @@ -1758,7 +1813,7 @@ } }, "rightExpression": { - "id": 26, + "id": 29, "kind": "", "startPos": { "offset": 72, @@ -1796,7 +1851,7 @@ "end": 73 }, "expression": { - "id": 25, + "id": 28, "kind": "", "startPos": { "offset": 73, @@ -1813,7 +1868,7 @@ "start": 73, "end": 74, "expression": { - "id": 24, + "id": 27, "kind": "", "startPos": { "offset": 73, @@ -1877,7 +1932,7 @@ } }, "rightExpression": { - "id": 29, + "id": 32, "kind": "", "startPos": { "offset": 77, @@ -1894,7 +1949,7 @@ "start": 77, "end": 78, "expression": { - "id": 28, + "id": 31, "kind": "", "startPos": { "offset": 77, @@ -1959,7 +2014,7 @@ "args": [] }, { - "id": 41, + "id": 44, "kind": "", "startPos": { "offset": 84, @@ -1976,7 +2031,7 @@ "start": 84, "end": 95, "callee": { - "id": 40, + "id": 43, "kind": "", "startPos": { "offset": 84, @@ -2036,7 +2091,7 @@ "end": 93 }, "leftExpression": { - "id": 37, + "id": 40, "kind": "", "startPos": { "offset": 84, @@ -2160,7 +2215,7 @@ }, "elementList": [ { - "id": 36, + "id": 39, "kind": "", "startPos": { "offset": 85, @@ -2220,7 +2275,7 @@ "end": 88 }, "leftExpression": { - "id": 33, + "id": 36, "kind": "", "startPos": { "offset": 85, @@ -2237,7 +2292,7 @@ "start": 85, "end": 86, "expression": { - "id": 32, + "id": 35, "kind": "", "startPos": { "offset": 85, @@ -2299,7 +2354,7 @@ } }, "rightExpression": { - "id": 35, + "id": 38, "kind": "", "startPos": { "offset": 89, @@ -2316,7 +2371,7 @@ "start": 89, "end": 90, "expression": { - "id": 34, + "id": 37, "kind": "", "startPos": { "offset": 89, @@ -2403,7 +2458,7 @@ } }, "rightExpression": { - "id": 39, + "id": 42, "kind": "", "startPos": { "offset": 94, @@ -2420,7 +2475,7 @@ "start": 94, "end": 95, "expression": { - "id": 38, + "id": 41, "kind": "", "startPos": { "offset": 94, @@ -2485,7 +2540,7 @@ "args": [] }, { - "id": 50, + "id": 53, "kind": "", "startPos": { "offset": 101, @@ -2502,7 +2557,7 @@ "start": 101, "end": 114, "callee": { - "id": 49, + "id": 52, "kind": "", "startPos": { "offset": 101, @@ -2562,7 +2617,7 @@ "end": 110 }, "leftExpression": { - "id": 46, + "id": 49, "kind": "", "startPos": { "offset": 101, @@ -2622,7 +2677,7 @@ "end": 104 }, "leftExpression": { - "id": 43, + "id": 46, "kind": "", "startPos": { "offset": 101, @@ -2639,7 +2694,7 @@ "start": 101, "end": 102, "expression": { - "id": 42, + "id": 45, "kind": "", "startPos": { "offset": 101, @@ -2786,7 +2841,7 @@ } }, "rightExpression": { - "id": 45, + "id": 48, "kind": "", "startPos": { "offset": 105, @@ -2803,7 +2858,7 @@ "start": 105, "end": 108, "expression": { - "id": 44, + "id": 47, "kind": "", "startPos": { "offset": 105, @@ -2866,7 +2921,7 @@ } }, "rightExpression": { - "id": 48, + "id": 51, "kind": "", "startPos": { "offset": 111, @@ -2883,7 +2938,7 @@ "start": 111, "end": 114, "expression": { - "id": 47, + "id": 50, "kind": "", "startPos": { "offset": 111, @@ -2948,7 +3003,7 @@ "args": [] }, { - "id": 59, + "id": 62, "kind": "", "startPos": { "offset": 122, @@ -2965,7 +3020,7 @@ "start": 122, "end": 141, "callee": { - "id": 58, + "id": 61, "kind": "", "startPos": { "offset": 122, @@ -3025,7 +3080,7 @@ "end": 134 }, "leftExpression": { - "id": 55, + "id": 58, "kind": "", "startPos": { "offset": 122, @@ -3085,7 +3140,7 @@ "end": 125 }, "leftExpression": { - "id": 52, + "id": 55, "kind": "", "startPos": { "offset": 122, @@ -3102,7 +3157,7 @@ "start": 122, "end": 123, "expression": { - "id": 51, + "id": 54, "kind": "", "startPos": { "offset": 122, @@ -3270,7 +3325,7 @@ } }, "rightExpression": { - "id": 54, + "id": 57, "kind": "", "startPos": { "offset": 131, @@ -3287,7 +3342,7 @@ "start": 131, "end": 132, "expression": { - "id": 53, + "id": 56, "kind": "", "startPos": { "offset": 131, @@ -3435,7 +3490,7 @@ } }, "rightExpression": { - "id": 57, + "id": 60, "kind": "", "startPos": { "offset": 140, @@ -3452,7 +3507,7 @@ "start": 140, "end": 141, "expression": { - "id": 56, + "id": 59, "kind": "", "startPos": { "offset": 140, @@ -3602,7 +3657,7 @@ "args": [] }, { - "id": 68, + "id": 71, "kind": "", "startPos": { "offset": 149, @@ -3619,7 +3674,7 @@ "start": 149, "end": 168, "callee": { - "id": 67, + "id": 70, "kind": "", "startPos": { "offset": 149, @@ -3764,7 +3819,7 @@ "end": 166 }, "leftExpression": { - "id": 64, + "id": 67, "kind": "", "startPos": { "offset": 149, @@ -3909,7 +3964,7 @@ "end": 157 }, "leftExpression": { - "id": 61, + "id": 64, "kind": "", "startPos": { "offset": 149, @@ -3926,7 +3981,7 @@ "start": 149, "end": 150, "expression": { - "id": 60, + "id": 63, "kind": "", "startPos": { "offset": 149, @@ -4094,7 +4149,7 @@ } }, "rightExpression": { - "id": 63, + "id": 66, "kind": "", "startPos": { "offset": 158, @@ -4111,7 +4166,7 @@ "start": 158, "end": 159, "expression": { - "id": 62, + "id": 65, "kind": "", "startPos": { "offset": 158, @@ -4174,7 +4229,7 @@ } }, "rightExpression": { - "id": 66, + "id": 69, "kind": "", "startPos": { "offset": 167, @@ -4191,7 +4246,7 @@ "start": 167, "end": 168, "expression": { - "id": 65, + "id": 68, "kind": "", "startPos": { "offset": 167, @@ -4256,7 +4311,7 @@ "args": [] }, { - "id": 77, + "id": 80, "kind": "", "startPos": { "offset": 176, @@ -4273,7 +4328,7 @@ "start": 176, "end": 181, "callee": { - "id": 76, + "id": 79, "kind": "", "startPos": { "offset": 176, @@ -4311,7 +4366,7 @@ "end": 180 }, "leftExpression": { - "id": 73, + "id": 76, "kind": "", "startPos": { "offset": 176, @@ -4349,7 +4404,7 @@ "end": 178 }, "leftExpression": { - "id": 70, + "id": 73, "kind": "", "startPos": { "offset": 176, @@ -4366,7 +4421,7 @@ "start": 176, "end": 177, "expression": { - "id": 69, + "id": 72, "kind": "", "startPos": { "offset": 176, @@ -4512,7 +4567,7 @@ } }, "rightExpression": { - "id": 72, + "id": 75, "kind": "", "startPos": { "offset": 178, @@ -4529,7 +4584,7 @@ "start": 178, "end": 179, "expression": { - "id": 71, + "id": 74, "kind": "", "startPos": { "offset": 178, @@ -4570,7 +4625,7 @@ } }, "rightExpression": { - "id": 75, + "id": 78, "kind": "", "startPos": { "offset": 180, @@ -4587,7 +4642,7 @@ "start": 180, "end": 181, "expression": { - "id": 74, + "id": 77, "kind": "", "startPos": { "offset": 180, @@ -4652,7 +4707,7 @@ "args": [] }, { - "id": 86, + "id": 89, "kind": "", "startPos": { "offset": 189, @@ -4669,7 +4724,7 @@ "start": 189, "end": 206, "callee": { - "id": 85, + "id": 88, "kind": "", "startPos": { "offset": 189, @@ -4729,7 +4784,7 @@ "end": 199 }, "leftExpression": { - "id": 82, + "id": 85, "kind": "", "startPos": { "offset": 189, @@ -4789,7 +4844,7 @@ "end": 191 }, "leftExpression": { - "id": 79, + "id": 82, "kind": "", "startPos": { "offset": 189, @@ -4806,7 +4861,7 @@ "start": 189, "end": 190, "expression": { - "id": 78, + "id": 81, "kind": "", "startPos": { "offset": 189, @@ -4952,7 +5007,7 @@ } }, "rightExpression": { - "id": 81, + "id": 84, "kind": "", "startPos": { "offset": 197, @@ -4969,7 +5024,7 @@ "start": 197, "end": 198, "expression": { - "id": 80, + "id": 83, "kind": "", "startPos": { "offset": 197, @@ -5095,7 +5150,7 @@ } }, "rightExpression": { - "id": 84, + "id": 87, "kind": "", "startPos": { "offset": 205, @@ -5112,7 +5167,7 @@ "start": 205, "end": 206, "expression": { - "id": 83, + "id": 86, "kind": "", "startPos": { "offset": 205, @@ -5262,7 +5317,7 @@ "args": [] }, { - "id": 95, + "id": 98, "kind": "", "startPos": { "offset": 214, @@ -5279,7 +5334,7 @@ "start": 214, "end": 226, "callee": { - "id": 94, + "id": 97, "kind": "", "startPos": { "offset": 214, @@ -5423,7 +5478,7 @@ "end": 225 }, "leftExpression": { - "id": 91, + "id": 94, "kind": "", "startPos": { "offset": 214, @@ -5461,7 +5516,7 @@ "end": 216 }, "leftExpression": { - "id": 88, + "id": 91, "kind": "", "startPos": { "offset": 214, @@ -5478,7 +5533,7 @@ "start": 214, "end": 215, "expression": { - "id": 87, + "id": 90, "kind": "", "startPos": { "offset": 214, @@ -5624,7 +5679,7 @@ } }, "rightExpression": { - "id": 90, + "id": 93, "kind": "", "startPos": { "offset": 216, @@ -5641,7 +5696,7 @@ "start": 216, "end": 217, "expression": { - "id": 89, + "id": 92, "kind": "", "startPos": { "offset": 216, @@ -5704,7 +5759,7 @@ } }, "rightExpression": { - "id": 93, + "id": 96, "kind": "", "startPos": { "offset": 225, @@ -5721,7 +5776,7 @@ "start": 225, "end": 226, "expression": { - "id": 92, + "id": 95, "kind": "", "startPos": { "offset": 225, @@ -5786,7 +5841,7 @@ "args": [] }, { - "id": 100, + "id": 103, "kind": "", "startPos": { "offset": 234, @@ -5803,7 +5858,7 @@ "start": 234, "end": 237, "callee": { - "id": 99, + "id": 102, "kind": "", "startPos": { "offset": 234, @@ -5820,7 +5875,7 @@ "start": 234, "end": 237, "callee": { - "id": 97, + "id": 100, "kind": "", "startPos": { "offset": 234, @@ -5837,7 +5892,7 @@ "start": 234, "end": 235, "expression": { - "id": 96, + "id": 99, "kind": "", "startPos": { "offset": 234, @@ -5983,7 +6038,7 @@ } }, "argumentList": { - "id": 98, + "id": 101, "kind": "", "startPos": { "offset": 235, @@ -6070,7 +6125,7 @@ "args": [] }, { - "id": 108, + "id": 111, "kind": "", "startPos": { "offset": 245, @@ -6087,7 +6142,7 @@ "start": 245, "end": 254, "callee": { - "id": 107, + "id": 110, "kind": "", "startPos": { "offset": 245, @@ -6232,7 +6287,7 @@ }, "elementList": [ { - "id": 102, + "id": 105, "kind": "", "startPos": { "offset": 246, @@ -6249,7 +6304,7 @@ "start": 246, "end": 247, "expression": { - "id": 101, + "id": 104, "kind": "", "startPos": { "offset": 246, @@ -6289,7 +6344,7 @@ } }, { - "id": 104, + "id": 107, "kind": "", "startPos": { "offset": 249, @@ -6306,7 +6361,7 @@ "start": 249, "end": 250, "expression": { - "id": 103, + "id": 106, "kind": "", "startPos": { "offset": 249, @@ -6346,7 +6401,7 @@ } }, { - "id": 106, + "id": 109, "kind": "", "startPos": { "offset": 252, @@ -6363,7 +6418,7 @@ "start": 252, "end": 253, "expression": { - "id": 105, + "id": 108, "kind": "", "startPos": { "offset": 252, @@ -6538,7 +6593,7 @@ "args": [] }, { - "id": 116, + "id": 119, "kind": "", "startPos": { "offset": 260, @@ -6555,7 +6610,7 @@ "start": 260, "end": 269, "callee": { - "id": 115, + "id": 118, "kind": "", "startPos": { "offset": 260, @@ -6679,7 +6734,7 @@ }, "elementList": [ { - "id": 110, + "id": 113, "kind": "", "startPos": { "offset": 261, @@ -6696,7 +6751,7 @@ "start": 261, "end": 262, "expression": { - "id": 109, + "id": 112, "kind": "", "startPos": { "offset": 261, @@ -6736,7 +6791,7 @@ } }, { - "id": 112, + "id": 115, "kind": "", "startPos": { "offset": 264, @@ -6753,7 +6808,7 @@ "start": 264, "end": 265, "expression": { - "id": 111, + "id": 114, "kind": "", "startPos": { "offset": 264, @@ -6793,7 +6848,7 @@ } }, { - "id": 114, + "id": 117, "kind": "", "startPos": { "offset": 267, @@ -6810,7 +6865,7 @@ "start": 267, "end": 268, "expression": { - "id": 113, + "id": 116, "kind": "", "startPos": { "offset": 267, @@ -6985,7 +7040,7 @@ "args": [] }, { - "id": 128, + "id": 131, "kind": "", "startPos": { "offset": 277, @@ -7002,7 +7057,7 @@ "start": 277, "end": 296, "callee": { - "id": 127, + "id": 130, "kind": "", "startPos": { "offset": 277, @@ -7147,7 +7202,7 @@ }, "elementList": [ { - "id": 126, + "id": 129, "kind": "", "startPos": { "offset": 278, @@ -7164,7 +7219,7 @@ "start": 278, "end": 295, "callee": { - "id": 118, + "id": 121, "kind": "", "startPos": { "offset": 278, @@ -7181,7 +7236,7 @@ "start": 278, "end": 279, "expression": { - "id": 117, + "id": 120, "kind": "", "startPos": { "offset": 278, @@ -7243,7 +7298,7 @@ } }, "argumentList": { - "id": 125, + "id": 128, "kind": "", "startPos": { "offset": 286, @@ -7388,7 +7443,7 @@ }, "elementList": [ { - "id": 120, + "id": 123, "kind": "", "startPos": { "offset": 287, @@ -7405,7 +7460,7 @@ "start": 287, "end": 288, "expression": { - "id": 119, + "id": 122, "kind": "", "startPos": { "offset": 287, @@ -7445,7 +7500,7 @@ } }, { - "id": 122, + "id": 125, "kind": "", "startPos": { "offset": 290, @@ -7462,7 +7517,7 @@ "start": 290, "end": 291, "expression": { - "id": 121, + "id": 124, "kind": "", "startPos": { "offset": 290, @@ -7502,7 +7557,7 @@ } }, { - "id": 124, + "id": 127, "kind": "", "startPos": { "offset": 293, @@ -7519,7 +7574,7 @@ "start": 293, "end": 294, "expression": { - "id": 123, + "id": 126, "kind": "", "startPos": { "offset": 293, @@ -7719,7 +7774,7 @@ "args": [] }, { - "id": 134, + "id": 137, "kind": "", "startPos": { "offset": 304, @@ -7736,7 +7791,7 @@ "start": 304, "end": 316, "callee": { - "id": 133, + "id": 136, "kind": "", "startPos": { "offset": 304, @@ -7881,7 +7936,7 @@ }, "elementList": [ { - "id": 130, + "id": 133, "kind": "", "startPos": { "offset": 305, @@ -7898,7 +7953,7 @@ "start": 305, "end": 306, "expression": { - "id": 129, + "id": 132, "kind": "", "startPos": { "offset": 305, @@ -7938,7 +7993,7 @@ } }, { - "id": 132, + "id": 135, "kind": "", "startPos": { "offset": 314, @@ -7955,7 +8010,7 @@ "start": 314, "end": 315, "expression": { - "id": 131, + "id": 134, "kind": "", "startPos": { "offset": 314, @@ -8193,7 +8248,7 @@ "args": [] }, { - "id": 153, + "id": 157, "kind": "", "startPos": { "offset": 324, @@ -8210,8 +8265,8 @@ "start": 324, "end": 348, "callee": { - "id": 152, - "kind": "", + "id": 139, + "kind": "", "startPos": { "offset": 324, "line": 37, @@ -8219,99 +8274,286 @@ }, "fullStart": 319, "endPos": { - "offset": 348, + "offset": 325, "line": 37, - "column": 28 + "column": 5 }, - "fullEnd": 350, + "fullEnd": 326, "start": 324, - "end": 348, - "op": { - "kind": "", + "end": 325, + "expression": { + "id": 138, + "kind": "", "startPos": { - "offset": 334, + "offset": 324, "line": 37, - "column": 14 + "column": 4 }, + "fullStart": 319, "endPos": { - "offset": 336, + "offset": 325, "line": 37, - "column": 16 + "column": 5 }, - "value": "!=", - "leadingTrivia": [], - "trailingTrivia": [ - { - "kind": "", - "startPos": { - "offset": 336, - "line": 37, - "column": 16 + "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 }, - "endPos": { - "offset": 337, - "line": 37, - "column": 17 + { + "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 }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 336, - "end": 337 - } - ], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 334, - "end": 336 + { + "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 + } + } + }, + "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 + } }, - "leftExpression": { - "id": 142, + { + "id": 148, "kind": "", "startPos": { - "offset": 324, + "offset": 328, "line": 37, - "column": 4 + "column": 8 }, - "fullStart": 319, + "fullStart": 328, "endPos": { - "offset": 333, + "offset": 338, "line": 37, - "column": 13 + "column": 18 }, - "fullEnd": 334, - "start": 324, - "end": 333, + "fullEnd": 339, + "start": 328, + "end": 338, "op": { "kind": "", "startPos": { - "offset": 330, + "offset": 334, "line": 37, - "column": 10 + "column": 14 }, "endPos": { - "offset": 331, + "offset": 336, "line": 37, - "column": 11 + "column": 16 }, - "value": "/", + "value": "!=", "leadingTrivia": [], "trailingTrivia": [ { "kind": "", "startPos": { - "offset": 331, + "offset": 336, "line": 37, - "column": 11 + "column": 16 }, "endPos": { - "offset": 332, + "offset": 337, "line": 37, - "column": 12 + "column": 17 }, "value": " ", "leadingTrivia": [], @@ -8319,59 +8561,59 @@ "leadingInvalid": [], "trailingInvalid": [], "isInvalid": false, - "start": 331, - "end": 332 + "start": 336, + "end": 337 } ], "leadingInvalid": [], "trailingInvalid": [], "isInvalid": false, - "start": 330, - "end": 331 + "start": 334, + "end": 336 }, "leftExpression": { - "id": 139, + "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": [], @@ -8379,203 +8621,18 @@ "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": 136, - "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": 135, - "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": 138, + "id": 142, "kind": "", "startPos": { "offset": 328, @@ -8592,7 +8649,7 @@ "start": 328, "end": 329, "expression": { - "id": 137, + "id": 141, "kind": "", "startPos": { "offset": 328, @@ -8652,28 +8709,10 @@ "end": 329 } } - } - }, - "rightExpression": { - "id": 141, - "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": 140, - "kind": "", + "rightExpression": { + "id": 144, + "kind": "", "startPos": { "offset": 332, "line": 37, @@ -8686,116 +8725,73 @@ "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": 151, - "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": "", + "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": 144, + "rightExpression": { + "id": 147, "kind": "", "startPos": { "offset": 337, @@ -8812,7 +8808,7 @@ "start": 337, "end": 338, "expression": { - "id": 143, + "id": 146, "kind": "", "startPos": { "offset": 337, @@ -8872,9 +8868,88 @@ "end": 338 } } + } + }, + { + "id": 156, + "kind": "", + "startPos": { + "offset": 339, + "line": 37, + "column": 19 }, - "rightExpression": { - "id": 150, + "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, @@ -8913,7 +8988,7 @@ }, "elementList": [ { - "id": 149, + "id": 154, "kind": "", "startPos": { "offset": 342, @@ -8973,7 +9048,7 @@ "end": 345 }, "leftExpression": { - "id": 146, + "id": 151, "kind": "", "startPos": { "offset": 342, @@ -8990,7 +9065,7 @@ "start": 342, "end": 343, "expression": { - "id": 145, + "id": 150, "kind": "", "startPos": { "offset": 342, @@ -9052,7 +9127,7 @@ } }, "rightExpression": { - "id": 148, + "id": 153, "kind": "", "startPos": { "offset": 346, @@ -9069,7 +9144,7 @@ "start": 346, "end": 347, "expression": { - "id": 147, + "id": 152, "kind": "", "startPos": { "offset": 346, @@ -9156,11 +9231,10 @@ } } } - }, - "args": [] + ] }, { - "id": 159, + "id": 163, "kind": "", "startPos": { "offset": 356, @@ -9177,7 +9251,7 @@ "start": 356, "end": 362, "callee": { - "id": 158, + "id": 162, "kind": "", "startPos": { "offset": 356, @@ -9237,7 +9311,7 @@ "end": 360 }, "leftExpression": { - "id": 155, + "id": 159, "kind": "", "startPos": { "offset": 356, @@ -9254,7 +9328,7 @@ "start": 356, "end": 357, "expression": { - "id": 154, + "id": 158, "kind": "", "startPos": { "offset": 356, @@ -9422,7 +9496,7 @@ } }, "rightExpression": { - "id": 157, + "id": 161, "kind": "", "startPos": { "offset": 361, @@ -9439,7 +9513,7 @@ "start": 361, "end": 362, "expression": { - "id": 156, + "id": 160, "kind": "", "startPos": { "offset": 361, @@ -9504,7 +9578,7 @@ "args": [] }, { - "id": 171, + "id": 175, "kind": "", "startPos": { "offset": 370, @@ -9521,7 +9595,7 @@ "start": 370, "end": 384, "callee": { - "id": 170, + "id": 174, "kind": "", "startPos": { "offset": 370, @@ -9581,7 +9655,7 @@ "end": 373 }, "leftExpression": { - "id": 161, + "id": 165, "kind": "", "startPos": { "offset": 370, @@ -9598,7 +9672,7 @@ "start": 370, "end": 371, "expression": { - "id": 160, + "id": 164, "kind": "", "startPos": { "offset": 370, @@ -9766,7 +9840,7 @@ } }, "rightExpression": { - "id": 169, + "id": 173, "kind": "", "startPos": { "offset": 374, @@ -9826,7 +9900,7 @@ "end": 378 }, "leftExpression": { - "id": 163, + "id": 167, "kind": "", "startPos": { "offset": 374, @@ -9843,7 +9917,7 @@ "start": 374, "end": 375, "expression": { - "id": 162, + "id": 166, "kind": "", "startPos": { "offset": 374, @@ -9905,7 +9979,7 @@ } }, "rightExpression": { - "id": 168, + "id": 172, "kind": "", "startPos": { "offset": 379, @@ -9965,7 +10039,7 @@ "end": 382 }, "leftExpression": { - "id": 165, + "id": 169, "kind": "", "startPos": { "offset": 379, @@ -9982,7 +10056,7 @@ "start": 379, "end": 380, "expression": { - "id": 164, + "id": 168, "kind": "", "startPos": { "offset": 379, @@ -10044,7 +10118,7 @@ } }, "rightExpression": { - "id": 167, + "id": 171, "kind": "", "startPos": { "offset": 383, @@ -10061,7 +10135,7 @@ "start": 383, "end": 384, "expression": { - "id": 166, + "id": 170, "kind": "", "startPos": { "offset": 383, @@ -10128,7 +10202,7 @@ "args": [] }, { - "id": 180, + "id": 184, "kind": "", "startPos": { "offset": 392, @@ -10145,7 +10219,7 @@ "start": 392, "end": 402, "callee": { - "id": 179, + "id": 183, "kind": "", "startPos": { "offset": 392, @@ -10205,7 +10279,7 @@ "end": 395 }, "leftExpression": { - "id": 173, + "id": 177, "kind": "", "startPos": { "offset": 392, @@ -10222,7 +10296,7 @@ "start": 392, "end": 393, "expression": { - "id": 172, + "id": 176, "kind": "", "startPos": { "offset": 392, @@ -10390,7 +10464,7 @@ } }, "rightExpression": { - "id": 178, + "id": 182, "kind": "", "startPos": { "offset": 396, @@ -10450,7 +10524,7 @@ "end": 400 }, "leftExpression": { - "id": 175, + "id": 179, "kind": "", "startPos": { "offset": 396, @@ -10467,7 +10541,7 @@ "start": 396, "end": 397, "expression": { - "id": 174, + "id": 178, "kind": "", "startPos": { "offset": 396, @@ -10529,7 +10603,7 @@ } }, "rightExpression": { - "id": 177, + "id": 181, "kind": "", "startPos": { "offset": 401, @@ -10546,7 +10620,7 @@ "start": 401, "end": 402, "expression": { - "id": 176, + "id": 180, "kind": "", "startPos": { "offset": 401, @@ -10612,7 +10686,7 @@ "args": [] }, { - "id": 214, + "id": 218, "kind": "", "startPos": { "offset": 410, @@ -10629,7 +10703,7 @@ "start": 410, "end": 458, "callee": { - "id": 213, + "id": 217, "kind": "", "startPos": { "offset": 410, @@ -10689,7 +10763,7 @@ "end": 414 }, "leftExpression": { - "id": 182, + "id": 186, "kind": "", "startPos": { "offset": 410, @@ -10706,7 +10780,7 @@ "start": 410, "end": 411, "expression": { - "id": 181, + "id": 185, "kind": "", "startPos": { "offset": 410, @@ -10874,7 +10948,7 @@ } }, "rightExpression": { - "id": 212, + "id": 216, "kind": "", "startPos": { "offset": 415, @@ -10997,7 +11071,7 @@ "end": 449 }, "leftExpression": { - "id": 201, + "id": 205, "kind": "", "startPos": { "offset": 415, @@ -11141,7 +11215,7 @@ "end": 432 }, "leftExpression": { - "id": 189, + "id": 193, "kind": "", "startPos": { "offset": 415, @@ -11201,7 +11275,7 @@ "end": 418 }, "leftExpression": { - "id": 184, + "id": 188, "kind": "", "startPos": { "offset": 415, @@ -11218,7 +11292,7 @@ "start": 415, "end": 416, "expression": { - "id": 183, + "id": 187, "kind": "", "startPos": { "offset": 415, @@ -11280,7 +11354,7 @@ } }, "rightExpression": { - "id": 188, + "id": 192, "kind": "", "startPos": { "offset": 419, @@ -11297,7 +11371,7 @@ "start": 419, "end": 423, "callee": { - "id": 186, + "id": 190, "kind": "", "startPos": { "offset": 419, @@ -11314,7 +11388,7 @@ "start": 419, "end": 420, "expression": { - "id": 185, + "id": 189, "kind": "", "startPos": { "offset": 419, @@ -11376,7 +11450,7 @@ } }, "argumentList": { - "id": 187, + "id": 191, "kind": "", "startPos": { "offset": 421, @@ -11462,7 +11536,7 @@ } }, "rightExpression": { - "id": 200, + "id": 204, "kind": "", "startPos": { "offset": 432, @@ -11500,7 +11574,7 @@ "end": 433 }, "expression": { - "id": 199, + "id": 203, "kind": "", "startPos": { "offset": 433, @@ -11538,7 +11612,7 @@ "end": 434 }, "expression": { - "id": 198, + "id": 202, "kind": "", "startPos": { "offset": 434, @@ -11576,7 +11650,7 @@ "end": 435 }, "expression": { - "id": 197, + "id": 201, "kind": "", "startPos": { "offset": 435, @@ -11614,7 +11688,7 @@ "end": 436 }, "expression": { - "id": 196, + "id": 200, "kind": "", "startPos": { "offset": 436, @@ -11652,7 +11726,7 @@ "end": 437 }, "expression": { - "id": 195, + "id": 199, "kind": "", "startPos": { "offset": 437, @@ -11690,7 +11764,7 @@ "end": 438 }, "expression": { - "id": 194, + "id": 198, "kind": "", "startPos": { "offset": 438, @@ -11728,7 +11802,7 @@ "end": 439 }, "expression": { - "id": 193, + "id": 197, "kind": "", "startPos": { "offset": 439, @@ -11766,7 +11840,7 @@ "end": 440 }, "expression": { - "id": 192, + "id": 196, "kind": "", "startPos": { "offset": 440, @@ -11804,7 +11878,7 @@ "end": 441 }, "expression": { - "id": 191, + "id": 195, "kind": "", "startPos": { "offset": 441, @@ -11821,7 +11895,7 @@ "start": 441, "end": 442, "expression": { - "id": 190, + "id": 194, "kind": "", "startPos": { "offset": 441, @@ -11893,7 +11967,7 @@ } }, "rightExpression": { - "id": 211, + "id": 215, "kind": "", "startPos": { "offset": 449, @@ -11931,7 +12005,7 @@ "end": 450 }, "expression": { - "id": 210, + "id": 214, "kind": "", "startPos": { "offset": 450, @@ -11969,7 +12043,7 @@ "end": 451 }, "expression": { - "id": 209, + "id": 213, "kind": "", "startPos": { "offset": 451, @@ -12007,7 +12081,7 @@ "end": 452 }, "expression": { - "id": 208, + "id": 212, "kind": "", "startPos": { "offset": 452, @@ -12045,7 +12119,7 @@ "end": 453 }, "expression": { - "id": 207, + "id": 211, "kind": "", "startPos": { "offset": 453, @@ -12083,7 +12157,7 @@ "end": 454 }, "expression": { - "id": 206, + "id": 210, "kind": "", "startPos": { "offset": 454, @@ -12121,7 +12195,7 @@ "end": 455 }, "expression": { - "id": 205, + "id": 209, "kind": "", "startPos": { "offset": 455, @@ -12159,7 +12233,7 @@ "end": 456 }, "expression": { - "id": 204, + "id": 208, "kind": "", "startPos": { "offset": 456, @@ -12197,7 +12271,7 @@ "end": 457 }, "expression": { - "id": 203, + "id": 207, "kind": "", "startPos": { "offset": 457, @@ -12214,7 +12288,7 @@ "start": 457, "end": 458, "expression": { - "id": 202, + "id": 206, "kind": "", "startPos": { "offset": 457, @@ -12358,117 +12432,181 @@ }, "errors": [ { - "code": 1008, - "diagnostic": "Unexpected '*' in an expression", + "code": 1007, + "diagnostic": "Expect a following space", "nodeOrToken": { - "kind": "", + "id": 2, + "kind": "", "startPos": { "offset": 23, "line": 1, "column": 4 }, + "fullStart": 19, "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 + "fullEnd": 24, + "start": 23, + "end": 24, + "token": { + "kind": "", + "startPos": { + "offset": 23, + "line": 1, + "column": 4 }, - { - "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": 24, + "line": 1, + "column": 5 }, - { - "kind": "", - "startPos": { - "offset": 21, - "line": 1, - "column": 2 - }, - "endPos": { - "offset": 22, - "line": 1, - "column": 3 + "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 }, - "value": " ", - "leadingTrivia": [], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": false, - "start": 21, - "end": 22 - }, - { - "kind": "", - "startPos": { - "offset": 22, - "line": 1, - "column": 3 + { + "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": 23, - "line": 1, - "column": 4 + { + "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": 22, - "end": 23 - } - ], - "trailingTrivia": [], - "leadingInvalid": [], - "trailingInvalid": [], - "isInvalid": true, - "start": 23, - "end": 24 + { + "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__/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, 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/package.json b/packages/dbml-parse/package.json index 0461384ce..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": "6.5.0", + "version": "7.0.0-alpha.1", "description": "DBML parser v2", "author": "Holistics ", "license": "Apache-2.0", @@ -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 ." 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/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/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/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 new file mode 100644 index 000000000..e4524a6d8 --- /dev/null +++ b/packages/dbml-parse/src/compiler/queries/transform/syncDiagramView.ts @@ -0,0 +1,250 @@ +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 { applyTextEdits, TextEdit } from './applyTextEdits'; +import { addDoubleQuoteIfNeeded } from '../utils'; + +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; + }; +} + +export interface DiagramViewBlock { + name: string; + startIndex: number; + endIndex: number; +} + +export function findDiagramViewBlocks (source: string): DiagramViewBlock[] { + const blocks: DiagramViewBlock[] = []; + const lexerResult = new Lexer(source).lex(); + if (lexerResult.getErrors().length > 0) return blocks; + + const tokens = lexerResult.getValue(); + const ast = new Parser(source, tokens, new SyntaxNodeIdGenerator()).parse(); + if (ast.getErrors().length > 0) return blocks; + + const program = ast.getValue().ast; + + for (const element of program.body) { + if (element.type?.value === ElementKind.DiagramView) { + const fragments = element.name + ? destructureComplexVariable(element.name).unwrap_or([]) + : []; + const name = fragments.length > 0 ? fragments[fragments.length - 1] : ''; + blocks.push({ + name, + startIndex: element.start, + endIndex: element.end, + }); + } + } + + return blocks; +} + +function generateDiagramViewBlock ( + name: string, + visibleEntities: DiagramViewSyncOperation['visibleEntities'], +): string { + const lines: string[] = [`DiagramView ${addDoubleQuoteIfNeeded(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 === DEFAULT_SCHEMA_NAME ? 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) + * @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[], + blocks?: DiagramViewBlock[], +): { newDbml: string; edits: TextEdit[] } { + const originalBlocks = blocks ?? findDiagramViewBlocks(dbml); + const allEdits: TextEdit[] = []; + + for (const op of operations) { + const edits = applyOperation(dbml, op, originalBlocks); + allEdits.push(...edits); + } + + // 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[], +): TextEdit[] { + switch (operation.operation) { + case 'create': + return computeCreateEdit(dbml, operation, blocks); + case 'update': + return computeUpdateEdit(dbml, operation, blocks); + case 'delete': + return computeDeleteEdit(dbml, operation, blocks); + default: + return []; + } +} + +function computeCreateEdit ( + dbml: string, + operation: DiagramViewSyncOperation, + blocks: DiagramViewBlock[], +): 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 computeUpdateEdit(dbml, operation, blocks); + } + + const newBlock = generateDiagramViewBlock(operation.name, operation.visibleEntities); + const appendText = '\n\n' + newBlock + '\n'; + return [{ + start: dbml.length, + end: dbml.length, + newText: appendText, + }]; +} + +function computeUpdateEdit ( + dbml: string, + operation: DiagramViewSyncOperation, + blocks: DiagramViewBlock[], +): TextEdit[] { + const block = blocks.find((b) => b.name === operation.name); + if (!block) return []; + + 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 edits; +} + +function computeDeleteEdit ( + dbml: string, + operation: DiagramViewSyncOperation, + blocks: DiagramViewBlock[], +): TextEdit[] { + const block = blocks.find((b) => b.name === operation.name); + if (!block) return []; + + // 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--; + } + // 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; + } + } + + // Expand forward to consume trailing newline + if (end < dbml.length && dbml[end] === '\r') end++; + if (end < dbml.length && dbml[end] === '\n') end++; + + return [{ start, end, newText: '' }]; +} 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/core/analyzer/binder/elementBinder/diagramView.ts b/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts new file mode 100644 index 000000000..1758cfa8b --- /dev/null +++ b/packages/dbml-parse/src/core/analyzer/binder/elementBinder/diagramView.ts @@ -0,0 +1,185 @@ +import { partition } from 'lodash-es'; +import { + 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'; +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 (isWildcardExpression(field.callee)) { + 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 (isWildcardExpression(field.callee)) { + 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 (isWildcardExpression(field.callee)) { + 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 (isWildcardExpression(field.callee)) { + return []; + } + + const bindees = scanNonListNodeForBinding(field.callee); + + return bindees.flatMap((bindee) => { + return lookupAndBindInScope(this.ast, bindee.variables.map((b) => ({ node: b, 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/analyzer/symbol/symbolIndex.ts b/packages/dbml-parse/src/core/analyzer/symbol/symbolIndex.ts index 35f191e16..66f38d9d3 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: @@ -72,10 +82,16 @@ 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: return createPartialInjectionSymbolIndex(key); + case SymbolKind.DiagramView: + return createDiagramViewSymbolIndex(key); + case SymbolKind.DiagramViewField: + return createDiagramViewFieldSymbolIndex(key); default: throw new Error('Unreachable'); } diff --git a/packages/dbml-parse/src/core/analyzer/symbol/symbols.ts b/packages/dbml-parse/src/core/analyzer/symbol/symbols.ts index 8ba24822a..616da0e2e 100644 --- a/packages/dbml-parse/src/core/analyzer/symbol/symbols.ts +++ b/packages/dbml-parse/src/core/analyzer/symbol/symbols.ts @@ -142,6 +142,49 @@ 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 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..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, @@ -23,6 +25,9 @@ import { EnumFieldSymbol, TablePartialSymbol, PartialInjectionSymbol, + StickyNoteSymbol, + DiagramViewSymbol, + DiagramViewFieldSymbol, } from './symbols'; // Given `name`, generate indexes with `name` and all possible kind @@ -38,6 +43,8 @@ export function generatePossibleIndexes (name: string): NodeSymbolIndex[] { createTableGroupFieldSymbolIndex, createTablePartialSymbolIndex, createPartialInjectionSymbolIndex, + createDiagramViewSymbolIndex, + createDiagramViewFieldSymbolIndex, ].map((f) => f(name)); } @@ -72,5 +79,14 @@ export function getSymbolKind (symbol: NodeSymbol): SymbolKind { if (symbol instanceof PartialInjectionSymbol) { return SymbolKind.PartialInjection; } + 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/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 { 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 new CompileError( + CompileErrorCode.UNEXPECTED_SETTINGS, + `Unknown '${name}' setting for DiagramView`, + attr, + ))); + } + return errors; + } + + validateBody (body?: FunctionApplicationNode | BlockExpressionNode): { errors: CompileError[], warnings: CompileWarning[] } { + if (!body) return { errors: [], warnings: [] }; + + if (body instanceof FunctionApplicationNode) { + 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); + const subResult = this.validateSubElements(subs as ElementDeclarationNode[]); + return { + errors: [...this.validateFields(fields as FunctionApplicationNode[]), ...subResult.errors], + warnings: subResult.warnings, + }; + } + + 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[] = []; + // 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[]): { errors: CompileError[], warnings: CompileWarning[] } { + const errors: CompileError[] = []; + const warnings: CompileWarning[] = []; + + 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; + } + + const subBlockResult = this.validateSubBlock(sub); + errors.push(...subBlockResult.errors); + warnings.push(...subBlockResult.warnings); + } + + return { errors, warnings }; + } + + private validateSubBlock (sub: ElementDeclarationNode): { errors: CompileError[], warnings: CompileWarning[] } { + const errors: CompileError[] = []; + const warnings: CompileWarning[] = []; + + if (!sub.body || !(sub.body instanceof BlockExpressionNode)) { + return { errors, warnings }; + } + + const body = sub.body as BlockExpressionNode; + + const hasWildcard = body.body.some( + (e) => e instanceof FunctionApplicationNode && isWildcardExpression(e.callee), + ); + const hasSpecificItems = body.body.some( + (e) => e instanceof FunctionApplicationNode && !isWildcardExpression(e.callee), + ); + + if (hasWildcard && hasSpecificItems) { + warnings.push(new CompileWarning( + CompileErrorCode.INVALID_DIAGRAMVIEW_FIELD, + `Wildcard (*) combined with specific items in ${sub.type?.value} block. Specific items will be ignored.`, + sub, + )); + } + + errors.push(...this.registerSubBlockFields(sub)); + + return { errors, warnings }; + } + + 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)) { + // 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); + + 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/elementValidators/enum.ts b/packages/dbml-parse/src/core/analyzer/validator/elementValidators/enum.ts index a4f9f74fe..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,9 +1,9 @@ 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, + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; import { isExpressionAQuotedString, isExpressionAVariableNode } from '@/core/parser/utils'; import { SyntaxToken } from '@/core/lexer/tokens'; @@ -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[] { @@ -51,6 +54,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)]; } @@ -170,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 e5e313c60..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, @@ -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'; @@ -32,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[] { @@ -57,6 +61,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)]; } @@ -179,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 a483e59d1..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,8 +1,8 @@ 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, + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, ProgramNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; import { SyntaxToken } from '@/core/lexer/tokens'; import { ElementValidator } from '@/core/analyzer/validator/types'; @@ -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 }; @@ -24,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[] { @@ -68,6 +72,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)]; @@ -82,7 +89,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 []; } @@ -140,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 a6fae1f79..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,8 +1,8 @@ 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, + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, ListExpressionNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; import { SyntaxToken } from '@/core/lexer/tokens'; import { ElementValidator } from '@/core/analyzer/validator/types'; @@ -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[] { @@ -36,6 +39,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)]; @@ -83,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 22da5b517..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,8 +1,8 @@ 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, + 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'; @@ -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. @@ -59,6 +62,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; @@ -255,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 cb2a999ad..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,9 +1,9 @@ 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, + BlockExpressionNode, ElementDeclarationNode, FunctionApplicationNode, IdentiferStreamNode, ListExpressionNode, ProgramNode, SyntaxNode, WildcardNode, } from '@/core/parser/nodes'; import { extractStringFromIdentifierStream, @@ -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[] { @@ -46,6 +49,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)]; @@ -175,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 56443d91f..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, @@ -14,6 +14,7 @@ import { PrefixExpressionNode, PrimaryExpressionNode, SyntaxNode, + WildcardNode, } from '@/core/parser/nodes'; import { destructureComplexVariable, extractVariableFromExpression, extractVarNameFromPrimaryVariable } from '@/core/analyzer/utils'; import { @@ -57,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[] { @@ -79,6 +83,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)]; } @@ -434,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 c63c8e6d0..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'; @@ -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'; @@ -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[] { @@ -56,6 +59,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, @@ -194,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 2372cfa66..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, @@ -11,6 +11,7 @@ import { ListExpressionNode, PrimaryExpressionNode, SyntaxNode, + WildcardNode, } from '@/core/parser/nodes'; import { destructureComplexVariable, extractVarNameFromPrimaryVariable } from '@/core/analyzer/utils'; import { @@ -53,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[] { @@ -83,6 +87,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, @@ -403,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/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/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/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, 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..cea9fe35b --- /dev/null +++ b/packages/dbml-parse/src/core/interpreter/elementInterpreter/diagramView.ts @@ -0,0 +1,187 @@ +import { partition } from 'lodash-es'; +import { destructureComplexVariable, extractReferee } from '@/core/analyzer/utils'; +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'; +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); + + 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) { + 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[] { + // 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)) { + this.diagramView.visibleEntities = { 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 []; + } + } + + const [subs] = partition(body.body, (e) => e instanceof ElementDeclarationNode); + 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 = []; + } + + // Store which dims were explicitly declared (normalize block type names to FilterConfig keys) + 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 []; + } + + private interpretSubBlock (body: BlockExpressionNode, blockType: string | undefined): void { + if (!blockType) return; + + // Check for wildcard + const hasWildcard = body.body.some( + (e) => e instanceof FunctionApplicationNode && isWildcardExpression(e.callee), + ); + + 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 = []; + envWildcards.add('tables'); + break; + case 'notes': + this.diagramView.visibleEntities!.stickyNotes = []; + envWildcards.add('stickyNotes'); + break; + case 'tablegroups': + this.diagramView.visibleEntities!.tableGroups = []; + envWildcards.add('tableGroups'); + break; + case 'schemas': + this.diagramView.visibleEntities!.schemas = []; + envWildcards.add('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; + + // 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; + + 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..e6b37d9c4 100644 --- a/packages/dbml-parse/src/core/interpreter/interpreter.ts +++ b/packages/dbml-parse/src/core/interpreter/interpreter.ts @@ -7,11 +7,13 @@ 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'; import { ElementKind } from '@/core/analyzer/types'; import { CompileWarning } from '../errors'; +import { DEFAULT_SCHEMA_NAME } from '@/constants'; function processColumnInDb (table: T): T { return { @@ -28,6 +30,34 @@ function processColumnInDb (table: T): T { }; } +/** + * Expand explicit wildcard ([]) for tableGroups in DiagramView visibleEntities + * to the concrete list of table group names. + * + * Only expands when: + * 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. + */ +function expandDiagramViewWildcards (env: InterpreterDatabase): void { + for (const view of env.diagramViews.values()) { + const ve = view.visibleEntities; + 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) { + const otherTrinitySet = explicitlySet.has('tables') || explicitlySet.has('schemas'); + if (!otherTrinitySet) { + ve.tableGroups = Array.from(env.tableGroups.values()).map((tg) => ({ + name: tg.name!, + })); + } + } + } +} + function convertEnvToDb (env: InterpreterDatabase): Database { // Convert records Map to array of TableRecord const records: TableRecord[] = []; @@ -62,6 +92,7 @@ function convertEnvToDb (env: InterpreterDatabase): Database { project: Array.from(env.project.values())[0] || {}, tablePartials: Array.from(env.tablePartials.values()).map(processColumnInDb), records, + diagramViews: Array.from(env.diagramViews.values()), }; } @@ -88,6 +119,9 @@ export default class Interpreter { recordsElements: [], cachedMergedTables: new Map(), source: ast.source, + diagramViews: new Map(), + diagramViewWildcards: new Map(), + diagramViewExplicitlySet: new Map(), }; } @@ -109,8 +143,9 @@ 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); return []; default: @@ -119,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 @@ -127,6 +163,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 4221aa728..6874d0aee 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,9 @@ export interface InterpreterDatabase { recordsElements: ElementDeclarationNode[]; cachedMergedTables: Map; // map Table to Table that has been merged with table partials source: string; + diagramViews: Map; + diagramViewWildcards: Map>; + diagramViewExplicitlySet: Map>; } // Record value type @@ -71,6 +94,7 @@ export interface Database { project: Project; tablePartials: TablePartial[]; records: TableRecord[]; + diagramViews: DiagramView[]; } export interface Table { 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 60d8c6251..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'; @@ -719,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, @@ -1196,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 70b179874..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'); } @@ -360,6 +365,12 @@ export function isExpressionAVariableNode ( ); } +// Return true if an expression node is a wildcard (*) +export function isWildcardExpression (node: SyntaxNode | undefined): boolean { + if (!node) return false; + return node instanceof WildcardNode; +} + // Return true if an expression node is a primary expression // with an identifier-like variable node export function isExpressionAnIdentifierNode (value?: unknown): value is PrimaryExpressionNode & { diff --git a/packages/dbml-parse/src/index.ts b/packages/dbml-parse/src/index.ts index 4601e4f88..f2c783105 100644 --- a/packages/dbml-parse/src/index.ts +++ b/packages/dbml-parse/src/index.ts @@ -56,6 +56,14 @@ export { type Project, type TableGroup, type TablePartial, + type DiagramView, + type FilterConfig, } from '@/core/interpreter/types'; +// 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'; + 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..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; @@ -572,6 +573,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 + && getElementKind(element.parent).unwrap_or(undefined) === ElementKind.DiagramView + ) { + return suggestInDiagramViewSubBlock(compiler, offset); + } + return noSuggestions(); + } default: return noSuggestions(); } @@ -579,7 +594,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 +813,69 @@ 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': { + 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 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] }; + } + default: + return noSuggestions(); + } +} + function suggestInIndex (compiler: Compiler, offset: number): CompletionList { return suggestColumnNameInIndexes(compiler, offset); }