build(server): count the bare datetime name the query builder takes

The datetime rule only matched a literal holding a call such as
datetime('now'). The query builder spells the same function as
eb.fn('datetime', [...]), where the literal is just the name, so that form
slipped through while strftime, json_extract and glob are caught bare. A
literal that is exactly datetime, julianday or unixepoch now counts as well,
the same way the glob rule treats a bare glob. The only such call lives in the
dialect layer, which is not scanned, so the baseline does not change.
This commit is contained in:
Maurice
2026-10-08 15:32:26 +02:00
parent 6ef6a7efd2
commit 1d6719347b
3 changed files with 5 additions and 2 deletions
+1 -1
View File
@@ -35,7 +35,7 @@ Single test: `npx vitest run tests/unit/nest/weather.controller.test.ts`, or `np
**Build emits even with type errors** (`scripts/build.mjs` catches `tsc` failures and warns). `npm run build` succeeding does **not** mean the code typechecks — run `npm run typecheck`.
**`lint:dialect`** (`scripts/dialect-lint.mjs`) reads every string and template literal under `src/` (comments do not count) and every `.insertId` read, and counts the SQLite-only spellings per file and rule: `INSERT OR IGNORE`/`REPLACE`, `datetime()`/`julianday()`, `strftime`, `json_extract` and its siblings, `GLOB`, `last_insert_rowid`, `AUTOINCREMENT`, a `PRAGMA` statement outside the `src/db/*.ts` lifecycle files and the migrations, and `||` concatenation in SQL. The counts are held to `scripts/dialect-baseline.json`; a new hit fails, and so does an entry above what its file holds now, so the change that removes one runs `npm run lint:dialect -- --update` (it never raises or adds an entry). `src/db/dialect/sql-functions.ts` is not scanned, and neither are the migrations up to `FROZEN_MIGRATIONS_THROUGH` in the script; a newer migration is held to the DML rules, while its DDL stays engine specific.
**`lint:dialect`** (`scripts/dialect-lint.mjs`) reads every string and template literal under `src/` (comments do not count) and every `.insertId` read, and counts the SQLite-only spellings per file and rule: `INSERT OR IGNORE`/`REPLACE`, `datetime()`/`julianday()`/`unixepoch()` (also as the bare name handed to `eb.fn`), `strftime`, `json_extract` and its siblings, `GLOB`, `last_insert_rowid`, `AUTOINCREMENT`, a `PRAGMA` statement outside the `src/db/*.ts` lifecycle files and the migrations, and `||` concatenation in SQL. The counts are held to `scripts/dialect-baseline.json`; a new hit fails, and so does an entry above what its file holds now, so the change that removes one runs `npm run lint:dialect -- --update` (it never raises or adds an entry). `src/db/dialect/sql-functions.ts` is not scanned, and neither are the migrations up to `FROZEN_MIGRATIONS_THROUGH` in the script; a newer migration is held to the DML rules, while its DDL stays engine specific.
## Architecture invariants
+3 -1
View File
@@ -58,7 +58,9 @@ export const RULES = [
},
{
name: 'datetime',
test: (text) => /\b(datetime|julianday|unixepoch)\s*\(/i.test(text),
// The call in SQL text, or the bare name the query builder takes in eb.fn('datetime', [...]).
test: (text) =>
/\b(datetime|julianday|unixepoch)\s*\(/i.test(text) || /^(datetime|julianday|unixepoch)$/i.test(text.trim()),
advice: 'use currentTimestamp/nowMinusDays/nowPlusSeconds and friends from sql-functions.ts',
migrations: true,
},
@@ -72,6 +72,7 @@ describe('dialect-lint.mjs', () => {
['insert-or', "void 'INSERT OR IGNORE INTO t (a) VALUES (?)';"],
['insert-or', 'void `INSERT OR REPLACE INTO t (a) VALUES (?)`;'],
['datetime', "void \"UPDATE t SET at = datetime('now')\";"],
['datetime', "declare function fn(name: string, args: string[]): void;\nfn('julianday', ['now']);"],
['strftime', "void `SELECT strftime('%s', 'now')`;"],
['json', "void 'SELECT json_extract(settings, ?) FROM users';"],
['glob', "void 'SELECT id FROM t WHERE name GLOB ?';"],