Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
3b8b84f
chore: bump vite in @dbml/parse and add vite to workspace root
H-DNA Nov 17, 2025
8e0d624
chore: add coverage npm script and ignore coverage/
H-DNA Nov 17, 2025
ec2e8d5
chore: remove vitest in @dbml/parse
H-DNA Nov 17, 2025
d4269c9
chore: setup coverage
H-DNA Nov 17, 2025
a4d9d22
chore: add coverage test to pr template
H-DNA Nov 17, 2025
156acb4
chore: add coverage to all packages and setup coverage report workflow
H-DNA Nov 17, 2025
f4c5b91
chore: remove upload artifact from coverage workflow
H-DNA Nov 17, 2025
d4ba49c
test(dbml-parse): move example-based tests to a dedicated folder
H-DNA Nov 17, 2025
6290916
test(dbml-parse): move tests to __tests__ for consistency
H-DNA Nov 17, 2025
489dbc4
fix(dbml-connector): properly close mssql client
H-DNA Nov 17, 2025
a889a65
test: refactor jest setup and migrate test to typescript
H-DNA Nov 17, 2025
5e12a02
chore(dbml-core): add tsparser to lint
H-DNA Nov 17, 2025
468a6fe
test: add lexer property-based tests
H-DNA Nov 17, 2025
18e0de2
test(dbml-core): prune some source files from coverage
H-DNA Nov 17, 2025
1f8deb2
chore: fix coverage workflow
H-DNA Nov 17, 2025
47d45c6
test(dbml-parse): fix wrong interpreter check test
H-DNA Nov 17, 2025
20d935e
test(dbml-parse): extract out arbitraries
H-DNA Nov 17, 2025
56dd5ca
chore: merge coverage and unit test workflow
H-DNA Nov 19, 2025
ac26121
test(dbml-parse): fix token arbitraries
H-DNA Nov 19, 2025
cd997a3
chore: remove wrong comment in collect coverage report
H-DNA Nov 19, 2025
c4f7178
test(dbml-parse): basic parser property tests
H-DNA Nov 19, 2025
19dff11
fix(dbml-parse): missing cases in markInvalid
H-DNA Nov 19, 2025
cde5d8b
fix(dbml-parse): more precisely check for failed variable extraction …
H-DNA Nov 19, 2025
2b74813
fix(dbml-parse): more precisely check for failed variable extraction …
H-DNA Nov 19, 2025
7926d34
fix(dbml-parse): allow nested call expression and array expression in…
H-DNA Nov 19, 2025
016dc24
test(dbml-parse): debug parser props tests
H-DNA Nov 19, 2025
b6a477d
fix(dbml-parse): include op in partial injection
H-DNA Nov 19, 2025
e1977f4
fix(dbml-parse): remove redundat assignment of e.partialNode
H-DNA Nov 19, 2025
0cfb068
fix(dbml-parse): use isInvalidToken instead of checking token.isInvalid
H-DNA Nov 19, 2025
e2542c3
fix(dbml-parse): wrong collection of invalid tokens in parser
H-DNA Nov 19, 2025
2b12262
fix(dbml-core): wrongly placed oracle test cases
H-DNA Nov 19, 2025
58706e0
fix(dbml-parse): properly interpret complex types that mixes multiple…
H-DNA Nov 19, 2025
ed52a81
test(dbml-parse): fix flattenTokens
H-DNA Nov 19, 2025
0b7f1f2
fix(dbml-parse): do not mark EOF as invalid
H-DNA Nov 19, 2025
8132605
test(dbml-parse): add one roundtrip prop for parser
H-DNA Nov 19, 2025
8e4f0c5
test(dbml-parse): use dbmlSchemaArbitrary instead of smallSchemaArbit…
H-DNA Nov 19, 2025
b377d0a
test(dbml-parse): add basic services tests
H-DNA Nov 19, 2025
17de0bd
test(dbml-parse): refactor tests
H-DNA Nov 20, 2025
2995ed5
fix: remove io-ts
H-DNA Nov 20, 2025
9886d35
test(dbml-parse): remove useless service util tests
H-DNA Nov 20, 2025
3609318
chore: set timeout for github action workflow
H-DNA Nov 20, 2025
190e177
chore: lower timeout to 10 min
H-DNA Nov 20, 2025
75c0b5e
test(dbml-parse): remove useless service fuzz tests
H-DNA Nov 20, 2025
de4b330
test(dbml-parse): add interpreter fuzzer tests and improve all exampl…
H-DNA Nov 20, 2025
8a47a18
fix(dbml-parse): add quote when suggesting empty strings
H-DNA Nov 20, 2025
4f2cd6c
test(dbml-parse): add service props tests
H-DNA Nov 20, 2025
8be31f1
test(dbml-parse): fix type errors
H-DNA Nov 20, 2025
57e8cef
Add renameTable function:
huyphung1602 Dec 16, 2025
cbebfd9
Merge branch 'master' into add-rename-table-function
huyphung1602 Jan 6, 2026
d1b0bfb
Refactor code and move the renameTable into dbml/parse
huyphung1602 Jan 7, 2026
8a33990
Fix failed test
huyphung1602 Jan 7, 2026
3b81582
refactor(dbml-parse): rename lib to core for clarity
H-DNA Jan 7, 2026
7783fe5
refactor(dbml-parse): break down compiler
H-DNA Jan 7, 2026
502f256
fix: properly call renameTable in @dbml/core
H-DNA Jan 7, 2026
d820ce7
refactor(dbml-parse): extract out apply text edits to a compiler query
H-DNA Jan 7, 2026
87b21cb
refactor(dbml-core): group the renameTable in @dbml/core to transformer
H-DNA Jan 7, 2026
c0ff96d
refactor(dbml-parse): extract `public` to constants
H-DNA Jan 7, 2026
ba7a4f6
fix: properly handle table.alias containing special chars
H-DNA Jan 7, 2026
35556b4
fix: hide unnecessary properties of compilers
H-DNA Jan 7, 2026
380ecc8
refactor(dbml-parse): simplify the compiler split
H-DNA Jan 7, 2026
c70bec5
fix: inline ContainerTokenResult
H-DNA Jan 8, 2026
2aaa447
refactor: take the method name to be the query name
H-DNA Jan 8, 2026
2919402
refactor: rename internal queries
H-DNA Jan 8, 2026
899ca3d
refactor: simplify compiler
H-DNA Jan 8, 2026
4fdaa25
fix: denest renameTable
H-DNA Jan 8, 2026
6adda5b
feat: add renameTable to types
H-DNA Jan 8, 2026
5050aa9
test: add some more rename table test
H-DNA Jan 8, 2026
89310e3
fix: add some FIXME comment & fix isValidIdentifier
H-DNA Jan 8, 2026
bdee6b5
fix: exports of renameTable
H-DNA Jan 8, 2026
ffecf24
fix: minor errors
H-DNA Jan 8, 2026
6d53ddf
fix: make the cache key more robust
H-DNA Jan 8, 2026
d437c53
fix: update signature of renameTable to be more robust
H-DNA Jan 8, 2026
a0f2eb5
doc: add FIXME
H-DNA Jan 8, 2026
cf6cec8
fix: allow both bare string and object in renameTable
H-DNA Jan 8, 2026
9684146
test: add more renameTable tests
H-DNA Jan 8, 2026
4f81fc9
Merge branch 'add-rename-table-function' into test/add-comprehensive-…
H-DNA Jan 8, 2026
a03468f
test: reorganize tests
H-DNA Jan 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 3 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
Please check directly on the box once each of these are done

- [ ] Documentation (if necessary)
- [ ] Lint checks
- [ ] Tests (integration test/unit test)
- [ ] Lint Checks Passed
- [ ] Unit Tests Passed
- [ ] Coverage Tests Passed
- [ ] Integration Tests Passed
- [ ] Code Review
1 change: 1 addition & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
jobs:
lint:
runs-on: ubuntu-22.04
timeout-minutes: 10
strategy:
matrix:
node-version: [22.x]
Expand Down
224 changes: 224 additions & 0 deletions .github/workflows/scripts/collect-coverage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
#!/usr/bin/env node

const fs = require('fs');
const path = require('path');

const PACKAGES = ['dbml-cli', 'dbml-connector', 'dbml-core', 'dbml-parse'];
const COVERAGE_THRESHOLD = 80; // Warning threshold for coverage

function readCoverageSummary(packageName) {
const summaryPath = path.join(__dirname, '../../../packages', packageName, 'coverage', 'coverage-summary.json');

if (!fs.existsSync(summaryPath)) {
console.error(`Warning: No coverage summary found for ${packageName}`);
return null;
}

const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
return summary;
}

function formatPercentage(value) {
return value.toFixed(2) + '%';
}

function getIcon(percentage) {
if (percentage >= 80) return '✅';
if (percentage >= 50) return '⚠️';
return '❌';
}

function getFilesWithLowCoverage(coverageData, packageName, threshold = 80) {
const files = [];

for (const [filePath, coverage] of Object.entries(coverageData)) {
if (filePath === 'total') continue;

// Check if any metric is below threshold
const linePct = coverage.lines.pct;
const stmtPct = coverage.statements.pct;
const funcPct = coverage.functions.pct;
const branchPct = coverage.branches.pct;

if (linePct < threshold || stmtPct < threshold || funcPct < threshold || branchPct < threshold) {
// Get relative path from package directory
const packageDir = path.join('packages', packageName);
const relativePath = path.relative(path.join(process.cwd(), packageDir), filePath);

files.push({
path: relativePath,
lines: linePct,
statements: stmtPct,
functions: funcPct,
branches: branchPct,
});
}
}

// Sort by lowest line coverage first
files.sort((a, b) => a.lines - b.lines);

return files;
}

function generateMarkdownReport(coverageData, commitSha) {
let markdown = `## Coverage Report\n\n`;
markdown += `**Commit:** ${commitSha}\n\n`;

// Add artifact link if running in GitHub Actions
if (process.env.GITHUB_RUN_ID && process.env.GITHUB_REPOSITORY) {
const artifactUrl = `https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${process.env.GITHUB_RUN_ID}`;
markdown += `📊 [View detailed HTML coverage reports](${artifactUrl})\n\n`;
}

let overallLines = { total: 0, covered: 0 };
let overallBranches = { total: 0, covered: 0 };
let overallFunctions = { total: 0, covered: 0 };
let overallStatements = { total: 0, covered: 0 };

// Calculate overall coverage
for (const pkg of coverageData) {
if (!pkg.coverageData || !pkg.coverageData.total) continue;

overallLines.total += pkg.coverageData.total.lines.total;
overallLines.covered += pkg.coverageData.total.lines.covered;
overallBranches.total += pkg.coverageData.total.branches.total;
overallBranches.covered += pkg.coverageData.total.branches.covered;
overallFunctions.total += pkg.coverageData.total.functions.total;
overallFunctions.covered += pkg.coverageData.total.functions.covered;
overallStatements.total += pkg.coverageData.total.statements.total;
overallStatements.covered += pkg.coverageData.total.statements.covered;
}

const overallLinePct = (overallLines.covered / overallLines.total) * 100;
const overallBranchPct = (overallBranches.covered / overallBranches.total) * 100;
const overallFunctionPct = (overallFunctions.covered / overallFunctions.total) * 100;
const overallStatementPct = (overallStatements.covered / overallStatements.total) * 100;

markdown += `### Overall Coverage\n\n`;
markdown += `| Metric | Coverage |\n`;
markdown += `|--------|----------|\n`;
markdown += `| Lines | ${getIcon(overallLinePct)} ${formatPercentage(overallLinePct)} (${overallLines.covered}/${overallLines.total}) |\n`;
markdown += `| Statements | ${getIcon(overallStatementPct)} ${formatPercentage(overallStatementPct)} (${overallStatements.covered}/${overallStatements.total}) |\n`;
markdown += `| Functions | ${getIcon(overallFunctionPct)} ${formatPercentage(overallFunctionPct)} (${overallFunctions.covered}/${overallFunctions.total}) |\n`;
markdown += `| Branches | ${getIcon(overallBranchPct)} ${formatPercentage(overallBranchPct)} (${overallBranches.covered}/${overallBranches.total}) |\n\n`;

markdown += `### Package Coverage\n\n`;
markdown += `| Package | Lines | Statements | Functions | Branches |\n`;
markdown += `|---------|-------|------------|-----------|----------|\n`;

for (const pkg of coverageData) {
if (!pkg.coverageData || !pkg.coverageData.total) {
markdown += `| @dbml/${pkg.name} | N/A | N/A | N/A | N/A |\n`;
continue;
}

const linePct = pkg.coverageData.total.lines.pct;
const stmtPct = pkg.coverageData.total.statements.pct;
const funcPct = pkg.coverageData.total.functions.pct;
const branchPct = pkg.coverageData.total.branches.pct;

markdown += `| @dbml/${pkg.name} | ${getIcon(linePct)} ${formatPercentage(linePct)} | ${getIcon(stmtPct)} ${formatPercentage(stmtPct)} | ${getIcon(funcPct)} ${formatPercentage(funcPct)} | ${getIcon(branchPct)} ${formatPercentage(branchPct)} |\n`;
}

markdown += `\n`;

// Add warnings for packages below threshold
const lowCoveragePackages = coverageData.filter(pkg =>
pkg.coverageData && pkg.coverageData.total && pkg.coverageData.total.lines.pct < COVERAGE_THRESHOLD
);

if (lowCoveragePackages.length > 0) {
markdown += `### ⚠️ Coverage Warnings\n\n`;
markdown += `The following packages have coverage below ${COVERAGE_THRESHOLD}%:\n\n`;
for (const pkg of lowCoveragePackages) {
markdown += `- **@dbml/${pkg.name}**: ${formatPercentage(pkg.coverageData.total.lines.pct)} line coverage\n`;
}
markdown += `\n`;
}

// Add file-level low coverage reporting
markdown += `### Files with Coverage Below ${COVERAGE_THRESHOLD}%\n\n`;

let hasLowCoverageFiles = false;

for (const pkg of coverageData) {
if (!pkg.coverageData) continue;

const lowCoverageFiles = getFilesWithLowCoverage(pkg.coverageData, pkg.name, COVERAGE_THRESHOLD);

if (lowCoverageFiles.length > 0) {
hasLowCoverageFiles = true;

markdown += `#### @dbml/${pkg.name}\n\n`;
markdown += '<details>\n';
markdown += `<summary>${lowCoverageFiles.length} file(s) below ${COVERAGE_THRESHOLD}% coverage</summary>\n\n`;
markdown += '| File | Lines | Statements | Functions | Branches |\n';
markdown += '|------|-------|------------|-----------|----------|\n';

for (const file of lowCoverageFiles) {
markdown += `| \`${file.path}\` | ${formatPercentage(file.lines)} | ${formatPercentage(file.statements)} | ${formatPercentage(file.functions)} | ${formatPercentage(file.branches)} |\n`;
}

markdown += '\n</details>\n\n';
}
}

if (!hasLowCoverageFiles) {
markdown += `All files have coverage at or above ${COVERAGE_THRESHOLD}%! 🎉\n\n`;
}

// Set GitHub Actions outputs
if (process.env.GITHUB_OUTPUT) {
const output = [
`overall-lines=${overallLinePct.toFixed(2)}`,
`overall-branches=${overallBranchPct.toFixed(2)}`,
`overall-functions=${overallFunctionPct.toFixed(2)}`,
`overall-statements=${overallStatementPct.toFixed(2)}`,
].join('\n');

fs.appendFileSync(process.env.GITHUB_OUTPUT, output + '\n');
}

return markdown;
}

function main() {
// Get commit SHA from environment or git
const commitSha = process.env.GITHUB_SHA ||
require('child_process').execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();

console.log('Collecting coverage data from packages...');

const coverageData = PACKAGES.map(packageName => {
console.log(`- Reading coverage for ${packageName}...`);
const coverageData = readCoverageSummary(packageName);
return {
name: packageName,
coverageData
};
});

console.log('\nGenerating coverage report...');
const markdown = generateMarkdownReport(coverageData, commitSha.substring(0, 8));

// Write markdown report
const reportPath = path.join(__dirname, '../../../coverage-report.md');
fs.writeFileSync(reportPath, markdown);
console.log(`\nCoverage report written to: ${reportPath}`);

// Output to console
console.log('\n' + markdown);

const criticallyLowCoverage = coverageData.some(pkg =>
pkg.coverageData && pkg.coverageData.total && pkg.coverageData.total.lines.pct < 60
);

if (criticallyLowCoverage) {
console.error('\n❌ Critical: One or more packages have coverage below 60%');
}
}

if (require.main === module) {
main();
}
22 changes: 19 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Run unit test
name: Run unit test and collect coverage
on:
push:
branches: [master]
Expand All @@ -12,6 +12,7 @@ on:
jobs:
build:
runs-on: ubuntu-22.04
timeout-minutes: 10
strategy:
matrix:
node-version: [22.x]
Expand Down Expand Up @@ -96,5 +97,20 @@ jobs:
run: yarn install --frozen-lockfile
- name: Build packages
run: yarn build
- name: Run unit tests
run: yarn test
- name: Run unit test and coverage
run: yarn coverage
- name: Generate coverage report
id: coverage
run: node .github/workflows/scripts/collect-coverage.js
- name: Upload coverage HTML reports
uses: actions/upload-artifact@v4
with:
name: coverage-reports
path: |
packages/*/coverage
- name: Comment PR with coverage report
uses: marocchino/sticky-pull-request-comment@v2
if: github.event_name == 'pull_request'
with:
recreate: true
path: coverage-report.md
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@ packages/dbml-core/benchmarks/*
tsconfig.tsbuildinfo

packages/dbml-connector/keys

# coverage
coverage/
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,21 @@
"build": "yarn clean-build && npx lerna run build",
"clean-build": "rm -rf ./packages/dbml-cli/lib ./packages/dbml-core/lib ./packages/dbml-parse/dist ./packages/dbml-connector/dist",
"lint": "npx lerna run lint",
"lint:fix": "npx lerna run lint:fix"
"lint:fix": "npx lerna run lint:fix",
"coverage": "npx lerna run coverage"
},
"devDependencies": {
"@babel/core": "^7.21.4",
"@babel/plugin-transform-runtime": "^7.21.4",
"@babel/preset-env": "^7.21.4",
"@glen/jest-raw-loader": "^2.0.0",
"@vitest/coverage-v8": "^4.0.9",
"babel-jest": "^29.5.0",
"fast-check": "^4.3.0",
"jest": "^29.5.0",
"lerna": "^7.1.4",
"lerna-changelog": "^2.2.0"
"lerna-changelog": "^2.2.0",
"vitest": "^4.0.9"
},
"resolutions": {
"**/tmp": "0.2.5"
Expand All @@ -32,10 +36,6 @@
"url": "https://github.com/holistics/dbml"
},
"jest": {
"setupFiles": [
"./packages/dbml-cli/jestHelpers.js",
"./packages/dbml-core/jestHelpers.js"
],
"setupFilesAfterEnv": [
"<rootDir>/test/testSetupFile.js"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import fs from 'fs';
import util from 'util';
import childProcess from 'child_process';
import stripAnsi from 'strip-ansi';
import { scanDirNames } from './testHelpers';

const exec = util.promisify(childProcess.exec);

describe('@dbml/cli', () => {
const runTest = async (dirName, binFile) => {
const runTest = async (dirName: string, binFile: string) => {
process.chdir(dirName);
const args = [path.join(__dirname, binFile)];
const optsRaw = fs.readFileSync(path.join(dirName, './options.json'), 'utf-8');
Expand Down
8 changes: 8 additions & 0 deletions packages/dbml-cli/__tests__/testHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import fs from 'fs';
import path from 'path';

export function scanDirNames (dirname: string, subpath: string): string[] {
const dirPath = path.join(dirname, subpath);
const dirs = fs.readdirSync(dirPath);
return dirs;
};
7 changes: 4 additions & 3 deletions packages/dbml-cli/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { type Config } from 'jest';

const config: Config = {
testMatch: ["**/?(*.)+(spec|test).?([mc])[jt]s?(x)"],
setupFiles: [
"./jestHelpers.js",
],
preset: 'ts-jest',
transform: {
"^.+\\.js$": "babel-jest",
},
collectCoverage: true,
coverageReporters: ["json", "json-summary", "html", "text"],
coverageDirectory: "coverage",
};

export default config;
8 changes: 0 additions & 8 deletions packages/dbml-cli/jestHelpers.js

This file was deleted.

4 changes: 3 additions & 1 deletion packages/dbml-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"main": "lib/index.js",
"license": "Apache-2.0",
"scripts": {
"test": "jest",
"test": "jest --coverage=false",
"coverage": "jest --coverage",
"build": "babel src --out-dir lib --copy-files",
"prepublish": "npm run build",
"lint": "eslint .",
Expand Down Expand Up @@ -55,6 +56,7 @@
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-jest": "^29.0.1",
"jest": "^29.5.0",
"ts-jest": "^29.4.5",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.3"
},
Expand Down
4 changes: 2 additions & 2 deletions packages/dbml-connector/__tests__/connector.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'fs';
import path from 'path';
import { scanDirNames } from '../jestHelpers.ts';
import { connector } from '../src/index.ts';
import { scanDirNames } from './testHelpers';
import { connector } from '../src/index';

const sortKeys = (obj: any): any => {
if (Array.isArray(obj)) {
Expand Down
Loading
Loading