fix(db): close round-6 review evasions in writer-coverage assertion
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
stripExprDressing also strips trailing non-null assertions and satisfies clauses, so export-default dressing (companies!, createDb satisfies T, (x as unknown)!) resolves at both ends. The three ns-member right-hand sides tolerate parenthesization. Every verb matcher admits the optional- call form (?.() via a shared CALL_OPEN shape. A computed member call whose key is a text-only template literal fails closed (its text never reaches lexed code); interpolated keys stay under the non-literal residual. Document the DB_FACTORY_IMPORTERS ns-destructure enumeration blind spot as a residual. Controls E69-E81.
This commit is contained in:
@@ -79,8 +79,20 @@
|
|||||||
* residual is accepted and reviews of DI provider modules carry it.
|
* residual is accepted and reviews of DI provider modules carry it.
|
||||||
* - Computed member access with a NON-literal name (obj[verb]()) is not
|
* - Computed member access with a NON-literal name (obj[verb]()) is not
|
||||||
* statically resolvable; literal computed access (obj['insert']()) is
|
* statically resolvable; literal computed access (obj['insert']()) is
|
||||||
* flagged. Constructing the verb at runtime is adjacent to eval and is
|
* flagged, and a computed member CALL whose key is a text-only template
|
||||||
* expected to be caught in review.
|
* literal (obj[`insert`]()) fails closed anywhere — template text never
|
||||||
|
* reaches the lexer's code output, so such a call is indistinguishable
|
||||||
|
* from a runtime-constructed verb (a template key WITH interpolation is
|
||||||
|
* a non-literal computed member, above). Constructing the verb at
|
||||||
|
* runtime is adjacent to eval and is expected to be caught in review.
|
||||||
|
* - The DB_FACTORY_IMPORTERS enumeration counts the import edges
|
||||||
|
* hasSymbolImportEdge can see (named import, literal dynamic package
|
||||||
|
* import with symbol use, namespace member use). Destructuring a factory
|
||||||
|
* out of a schema namespace (`const { createDb } = dbns`) confers
|
||||||
|
* capability for prong (iii) but is NOT visible to the enumeration, so a
|
||||||
|
* file on that route joins the importer set only via review; prong (iii)
|
||||||
|
* still flags any execution in it. The enumeration is a measured set,
|
||||||
|
* not a soundness claim.
|
||||||
* - General VALUE FLOW is not modeled. The analyzer tracks import edges,
|
* - General VALUE FLOW is not modeled. The analyzer tracks import edges,
|
||||||
* re-export chains, and single-step alias/destructure derivations from a
|
* re-export chains, and single-step alias/destructure derivations from a
|
||||||
* namespace binding (to depth 2) — not arbitrary assignment chains,
|
* namespace binding (to depth 2) — not arbitrary assignment chains,
|
||||||
@@ -456,9 +468,11 @@ const MEMBER_SEG = `(?:${DOT}\\w+|${BRACKET_OPEN}\\s*['"\`]\\w+['"\`]\\s*\\])`;
|
|||||||
const memberTail = (alt: string): string =>
|
const memberTail = (alt: string): string =>
|
||||||
`(?:${DOT}(?:${alt})\\b|${BRACKET_OPEN}\\s*['"\`](?:${alt})['"\`]\\s*\\])`;
|
`(?:${DOT}(?:${alt})\\b|${BRACKET_OPEN}\\s*['"\`](?:${alt})['"\`]\\s*\\])`;
|
||||||
/**
|
/**
|
||||||
* Reduce an exported expression to its core: strip parenthesization and
|
* Reduce an exported expression to its core: strip parenthesization,
|
||||||
* trailing type assertions (`(companies)`, `companies as unknown as object`)
|
* trailing type assertions (`as …`, `satisfies …`), and trailing non-null
|
||||||
* so `export default <expr>` passes see the binding under the dressing.
|
* assertions (`(companies)`, `companies as unknown as object`,
|
||||||
|
* `companies satisfies object`, `companies!`, `(companies as any)!`) so
|
||||||
|
* `export default <expr>` passes see the binding under the dressing.
|
||||||
*/
|
*/
|
||||||
function stripExprDressing(raw: string): string {
|
function stripExprDressing(raw: string): string {
|
||||||
let expr = raw.trim();
|
let expr = raw.trim();
|
||||||
@@ -468,11 +482,16 @@ function stripExprDressing(raw: string): string {
|
|||||||
.replace(/^\(\s*/, '')
|
.replace(/^\(\s*/, '')
|
||||||
.replace(/\s*\)$/, '')
|
.replace(/\s*\)$/, '')
|
||||||
.replace(/\s+as\s+[^()]+$/, '')
|
.replace(/\s+as\s+[^()]+$/, '')
|
||||||
|
.replace(/\s+satisfies\s+[^()]+$/, '')
|
||||||
|
.replace(/\s*!+$/, '')
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Call-open shape tolerant of the optional-call form: `f(…)` or `f?.(…)`. */
|
||||||
|
const CALL_OPEN = `\\s*(?:\\?\\.)?\\s*\\(`;
|
||||||
|
|
||||||
interface FileFacts {
|
interface FileFacts {
|
||||||
rel: string;
|
rel: string;
|
||||||
code: string;
|
code: string;
|
||||||
@@ -572,7 +591,7 @@ function computeSchemaConduits(files: FileFacts[], fileSet: Set<string>): Schema
|
|||||||
for (const ns of aliases.namespaces) {
|
for (const ns of aliases.namespaces) {
|
||||||
for (const m of f.code.matchAll(
|
for (const m of f.code.matchAll(
|
||||||
new RegExp(
|
new RegExp(
|
||||||
`export\\s+(?:const|let|var)\\s+(\\w+)\\s*=\\s*${ns}${MEMBER_SEG}*${memberTail(CLASS_SYMBOLS.join('|'))}`,
|
`export\\s+(?:const|let|var)\\s+(\\w+)\\s*=\\s*\\(*\\s*${ns}${MEMBER_SEG}*${memberTail(CLASS_SYMBOLS.join('|'))}`,
|
||||||
'g',
|
'g',
|
||||||
),
|
),
|
||||||
)) {
|
)) {
|
||||||
@@ -587,8 +606,9 @@ function computeSchemaConduits(files: FileFacts[], fileSet: Set<string>): Schema
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The default slot is an export name like any other, whatever the
|
// The default slot is an export name like any other, whatever the
|
||||||
// expression dressing: bare local, parenthesized, type-asserted, or
|
// expression dressing: bare local, parenthesized, type-asserted
|
||||||
// a namespace member chain in dot or bracket form, semicolon or not.
|
// (`as`/`satisfies`), non-null-asserted (`companies!`), or a
|
||||||
|
// namespace member chain in dot or bracket form, semicolon or not.
|
||||||
for (const m of f.code.matchAll(/export\s+default\s+([^;\n]+)/g)) {
|
for (const m of f.code.matchAll(/export\s+default\s+([^;\n]+)/g)) {
|
||||||
const expr = stripExprDressing(m[1]!);
|
const expr = stripExprDressing(m[1]!);
|
||||||
if (/^\w+$/.test(expr)) {
|
if (/^\w+$/.test(expr)) {
|
||||||
@@ -714,7 +734,7 @@ function classAliases(f: FileFacts, conduits: SchemaConduits, fileSet: Set<strin
|
|||||||
}
|
}
|
||||||
for (const m of f.code.matchAll(
|
for (const m of f.code.matchAll(
|
||||||
new RegExp(
|
new RegExp(
|
||||||
`(?:const|let|var)\\s+(\\w+)\\s*=\\s*${ns}${MEMBER_SEG}*(?:${DOT}(\\w+)\\b|${BRACKET_OPEN}\\s*['"\`](\\w+)['"\`]\\s*\\])`,
|
`(?:const|let|var)\\s+(\\w+)\\s*=\\s*\\(*\\s*${ns}${MEMBER_SEG}*(?:${DOT}(\\w+)\\b|${BRACKET_OPEN}\\s*['"\`](\\w+)['"\`]\\s*\\])`,
|
||||||
'g',
|
'g',
|
||||||
),
|
),
|
||||||
)) {
|
)) {
|
||||||
@@ -831,7 +851,7 @@ function computeCapabilityConduits(
|
|||||||
for (const [ns, src] of nss) {
|
for (const [ns, src] of nss) {
|
||||||
for (const am of f.code.matchAll(
|
for (const am of f.code.matchAll(
|
||||||
new RegExp(
|
new RegExp(
|
||||||
`(?:const|let|var)\\s+(\\w+)\\s*=\\s*${ns}(?:${DOT}(\\w+)\\b|${BRACKET_OPEN}\\s*['"\`](\\w+)['"\`]\\s*\\])`,
|
`(?:const|let|var)\\s+(\\w+)\\s*=\\s*\\(*\\s*${ns}(?:${DOT}(\\w+)\\b|${BRACKET_OPEN}\\s*['"\`](\\w+)['"\`]\\s*\\])`,
|
||||||
'g',
|
'g',
|
||||||
),
|
),
|
||||||
)) {
|
)) {
|
||||||
@@ -928,11 +948,30 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
const inRegister = INFRA_REGISTER.includes(rel);
|
const inRegister = INFRA_REGISTER.includes(rel);
|
||||||
const isSchemaDefinition = rel === 'packages/db/src/schema.ts';
|
const isSchemaDefinition = rel === 'packages/db/src/schema.ts';
|
||||||
|
|
||||||
// Runtime code construction: fails anywhere. Covers direct calls and
|
// Runtime code construction: fails anywhere. Covers direct calls
|
||||||
// literal computed access (window['eval'], globalThis['Function']).
|
// (optional-call form included), and literal computed access
|
||||||
if (/\beval\s*\(|\bnew\s+Function\s*\(|\[\s*['"](eval|Function)['"]\s*\]/.test(code)) {
|
// (window['eval'], globalThis['Function']).
|
||||||
|
if (
|
||||||
|
new RegExp(
|
||||||
|
`\\beval${CALL_OPEN}|\\bnew\\s+Function\\s*\\(|\\[\\s*['"](eval|Function)['"]\\s*\\]`,
|
||||||
|
).test(code)
|
||||||
|
) {
|
||||||
violations.push({ file: rel, prong: 'code-construction', detail: 'eval/new Function' });
|
violations.push({ file: rel, prong: 'code-construction', detail: 'eval/new Function' });
|
||||||
}
|
}
|
||||||
|
// A computed member CALL whose key is a text-only template literal is
|
||||||
|
// invisible to every verb matcher (the lexer routes template text to
|
||||||
|
// spans, so the key survives in code as two ADJACENT backticks — the
|
||||||
|
// discriminator against backticks inside quoted-string prose, whose text
|
||||||
|
// stays in code). Runtime code construction's sibling: fail-closed
|
||||||
|
// anywhere. A template key WITH interpolation is a non-literal computed
|
||||||
|
// member (documented residual above).
|
||||||
|
if (new RegExp(`\\[\\s*\`\`\\s*\\]${CALL_OPEN}`).test(code)) {
|
||||||
|
violations.push({
|
||||||
|
file: rel,
|
||||||
|
prong: 'code-construction',
|
||||||
|
detail: 'template-literal computed member call',
|
||||||
|
});
|
||||||
|
}
|
||||||
// Dynamic import whose specifier is not a single string literal: the
|
// Dynamic import whose specifier is not a single string literal: the
|
||||||
// import graph becomes unanalyzable. Checked per call site, so a literal
|
// import graph becomes unanalyzable. Checked per call site, so a literal
|
||||||
// first fragment ('x' + y) does not slip past. A LITERAL import of a
|
// first fragment ('x' + y) does not slip past. A LITERAL import of a
|
||||||
@@ -975,7 +1014,7 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
// from a dynamic module import).
|
// from a dynamic module import).
|
||||||
if (!CREATE_REQUIRE_REGISTER.includes(rel)) {
|
if (!CREATE_REQUIRE_REGISTER.includes(rel)) {
|
||||||
const crNames = new Set<string>();
|
const crNames = new Set<string>();
|
||||||
if (/\bcreateRequire\s*\(/.test(code)) crNames.add('createRequire');
|
if (new RegExp(`\\bcreateRequire${CALL_OPEN}`).test(code)) crNames.add('createRequire');
|
||||||
for (const m of code.matchAll(/import\s*\{([^}]*)\}\s*from\s*['"](?:node:)?module['"]/g)) {
|
for (const m of code.matchAll(/import\s*\{([^}]*)\}\s*from\s*['"](?:node:)?module['"]/g)) {
|
||||||
const alias = /\bcreateRequire\s+as\s+(\w+)/.exec(m[1]!);
|
const alias = /\bcreateRequire\s+as\s+(\w+)/.exec(m[1]!);
|
||||||
if (alias) crNames.add(alias[1]!);
|
if (alias) crNames.add(alias[1]!);
|
||||||
@@ -1006,7 +1045,7 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const n of crNames) {
|
for (const n of crNames) {
|
||||||
if (n === 'createRequire' || new RegExp(`\\b${n}\\s*\\(`).test(code)) {
|
if (n === 'createRequire' || new RegExp(`\\b${n}${CALL_OPEN}`).test(code)) {
|
||||||
violations.push({ file: rel, prong: 'dynamic-import', detail: 'createRequire()' });
|
violations.push({ file: rel, prong: 'dynamic-import', detail: 'createRequire()' });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1033,7 +1072,7 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
// The argument prefix tolerates parenthesization, spread, and array
|
// The argument prefix tolerates parenthesization, spread, and array
|
||||||
// wrapping: .insert((companies)), .insert(...[companies]).
|
// wrapping: .insert((companies)), .insert(...[companies]).
|
||||||
const writeRe = new RegExp(
|
const writeRe = new RegExp(
|
||||||
`\\.\\s*(insert|update|delete)\\s*\\(\\s*(?:(?:\\(|\\[|\\.\\.\\.)\\s*)*(${targets.join('|')})`,
|
`\\.\\s*(insert|update|delete)${CALL_OPEN}\\s*(?:(?:\\(|\\[|\\.\\.\\.)\\s*)*(${targets.join('|')})`,
|
||||||
'g',
|
'g',
|
||||||
);
|
);
|
||||||
for (const m of code.matchAll(writeRe)) {
|
for (const m of code.matchAll(writeRe)) {
|
||||||
@@ -1051,7 +1090,10 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
// form (measured), so it fails outright. Write verbs fail outside the
|
// form (measured), so it fails outright. Write verbs fail outside the
|
||||||
// allowlist; execution verbs fail outside allowlist ∪ register.
|
// allowlist; execution verbs fail outside allowlist ∪ register.
|
||||||
if (!inAllowlist) {
|
if (!inAllowlist) {
|
||||||
const writeBracket = /\[\s*['"](insert|update|delete)['"]\s*\]\s*\(/g;
|
const writeBracket = new RegExp(
|
||||||
|
`\\[\\s*['"](insert|update|delete)['"]\\s*\\]${CALL_OPEN}`,
|
||||||
|
'g',
|
||||||
|
);
|
||||||
for (const m of code.matchAll(writeBracket)) {
|
for (const m of code.matchAll(writeBracket)) {
|
||||||
violations.push({
|
violations.push({
|
||||||
file: rel,
|
file: rel,
|
||||||
@@ -1060,7 +1102,9 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (!inRegister) {
|
if (!inRegister) {
|
||||||
for (const m of code.matchAll(/\[\s*['"](execute|query|unsafe)['"]\s*\]\s*\(/g)) {
|
for (const m of code.matchAll(
|
||||||
|
new RegExp(`\\[\\s*['"](execute|query|unsafe)['"]\\s*\\]${CALL_OPEN}`, 'g'),
|
||||||
|
)) {
|
||||||
violations.push({
|
violations.push({
|
||||||
file: rel,
|
file: rel,
|
||||||
prong: 'iii-raw-execution',
|
prong: 'iii-raw-execution',
|
||||||
@@ -1142,8 +1186,9 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
if (driverImport) {
|
if (driverImport) {
|
||||||
violations.push({ file: rel, prong: 'iii-raw-execution', detail: 'direct driver import' });
|
violations.push({ file: rel, prong: 'iii-raw-execution', detail: 'direct driver import' });
|
||||||
// A driver client exposes query/execute/unsafe as raw primitives:
|
// A driver client exposes query/execute/unsafe as raw primitives:
|
||||||
// flag them all on any receiver, any argument, spaced or not.
|
// flag them all on any receiver, any argument, spaced, optional-call
|
||||||
for (const m of code.matchAll(/\.\s*(execute|query|unsafe)\s*\(/g)) {
|
// (`?.(`) or not.
|
||||||
|
for (const m of code.matchAll(new RegExp(`\\.\\s*(execute|query|unsafe)${CALL_OPEN}`, 'g'))) {
|
||||||
violations.push({
|
violations.push({
|
||||||
file: rel,
|
file: rel,
|
||||||
prong: 'iii-raw-execution',
|
prong: 'iii-raw-execution',
|
||||||
@@ -1155,7 +1200,7 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
// the relational builder, and unrelated .query() methods are common),
|
// the relational builder, and unrelated .query() methods are common),
|
||||||
// so factory capability flags execute/unsafe on any receiver and query
|
// so factory capability flags execute/unsafe on any receiver and query
|
||||||
// only on the db/client-shaped receiver backstop below.
|
// only on the db/client-shaped receiver backstop below.
|
||||||
for (const m of code.matchAll(/\.\s*(execute|unsafe)\s*\(/g)) {
|
for (const m of code.matchAll(new RegExp(`\\.\\s*(execute|unsafe)${CALL_OPEN}`, 'g'))) {
|
||||||
violations.push({
|
violations.push({
|
||||||
file: rel,
|
file: rel,
|
||||||
prong: 'iii-raw-execution',
|
prong: 'iii-raw-execution',
|
||||||
@@ -1171,7 +1216,7 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
// access tolerates ?. and ! markers (this.db?.query(…)).
|
// access tolerates ?. and ! markers (this.db?.query(…)).
|
||||||
for (const m of code.matchAll(
|
for (const m of code.matchAll(
|
||||||
new RegExp(
|
new RegExp(
|
||||||
`(?:\\b(?:db|database|client|conn|connection|pool|pg|pglite|tx|trx)|\\[\\s*['"](?:db|database|client|conn|connection|pool|pg|pglite|tx|trx)['"]\\s*\\])${DOT}(execute|query|unsafe)\\s*\\(`,
|
`(?:\\b(?:db|database|client|conn|connection|pool|pg|pglite|tx|trx)|\\[\\s*['"](?:db|database|client|conn|connection|pool|pg|pglite|tx|trx)['"]\\s*\\])${DOT}(execute|query|unsafe)${CALL_OPEN}`,
|
||||||
'g',
|
'g',
|
||||||
),
|
),
|
||||||
)) {
|
)) {
|
||||||
@@ -1199,7 +1244,7 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
if (nsName) sqlAliases.add(`${nsName}\\s*\\.\\s*sql`);
|
if (nsName) sqlAliases.add(`${nsName}\\s*\\.\\s*sql`);
|
||||||
}
|
}
|
||||||
if (sqlAliases.size > 0) {
|
if (sqlAliases.size > 0) {
|
||||||
const rawRe = new RegExp(`\\b(${[...sqlAliases].join('|')})\\s*\\.\\s*raw\\s*\\(`);
|
const rawRe = new RegExp(`\\b(${[...sqlAliases].join('|')})\\s*\\.\\s*raw${CALL_OPEN}`);
|
||||||
if (rawRe.test(code)) {
|
if (rawRe.test(code)) {
|
||||||
violations.push({ file: rel, prong: 'iii-raw-execution', detail: 'sql.raw()' });
|
violations.push({ file: rel, prong: 'iii-raw-execution', detail: 'sql.raw()' });
|
||||||
}
|
}
|
||||||
@@ -1749,6 +1794,112 @@ describe('hierarchy writer coverage (contract 1 §6.3b)', () => {
|
|||||||
name: 'E68 optional-chained bracketed DI receiver',
|
name: 'E68 optional-chained bracketed DI receiver',
|
||||||
src: `export class R { async f(t: string) { await this['db']?.query('TRUNCATE ' + t); } }`,
|
src: `export class R { async f(t: string) { await this['db']?.query('TRUNCATE ' + t); } }`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'E69 non-null-asserted default export of schema symbol',
|
||||||
|
src: `import c from './evasion-mid26.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid26.ts',
|
||||||
|
src: `import { companies } from '@mosaicstack/db';\nexport default companies!;`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E70 satisfies-dressed default export of schema symbol',
|
||||||
|
src: `import c from './evasion-mid27.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid27.ts',
|
||||||
|
src: `import { companies } from '@mosaicstack/db';\nexport default companies satisfies object;`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E71 paren-plus-assertion-plus-non-null default export',
|
||||||
|
src: `import c from './evasion-mid28.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid28.ts',
|
||||||
|
src: `import { companies } from '@mosaicstack/db';\nexport default (companies as unknown)!;`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E72 non-null-asserted ns-member default export (dynamic ns)',
|
||||||
|
src: `import c from './evasion-mid29.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid29.ts',
|
||||||
|
src: `const mod = await import('@mosaicstack/db');\nexport default mod.companies!;`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E73 parenthesized ns-member export-const of schema symbol',
|
||||||
|
src: `import { co } from './evasion-mid30.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(co).values({}); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid30.ts',
|
||||||
|
src: `import * as ns from '@mosaicstack/db';\nexport const co = (ns.companies);`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E74 non-null-asserted default export of factory local',
|
||||||
|
src: `import mk from './evasion-mid31.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid31.ts',
|
||||||
|
src: `import { createDb } from '@mosaicstack/db';\nexport default createDb!;`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E75 non-null-asserted ns-member factory default export',
|
||||||
|
src: `import mk from './evasion-mid32.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid32.ts',
|
||||||
|
src: `import * as ns from '@mosaicstack/db';\nexport default ns.createDb!;`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E76 satisfies-dressed default export of factory local',
|
||||||
|
src: `import mk from './evasion-mid33.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid33.ts',
|
||||||
|
src: `import { createDb } from '@mosaicstack/db';\nexport default createDb satisfies typeof createDb;`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E77 parenthesized ns-member factory derivation re-exported',
|
||||||
|
src: `import mk from './evasion-mid34.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid34.ts',
|
||||||
|
src: `import * as ns from '@mosaicstack/db';\nconst mk = (ns.createDb);\nexport default mk;`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E78 optional-call write verb',
|
||||||
|
src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert?.(companies).values({}); }`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E79 optional-call DI receiver chain',
|
||||||
|
src: `export class R { constructor(private pool?: { execute(s: string): Promise<unknown> }) {}\n async f(t: string) { await this.pool?.execute?.('TRUNCATE ' + t); } }`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E80 optional-call execution in factory-capable file',
|
||||||
|
src: `import { createDb } from '@mosaicstack/db';\nexport async function f(t: string) { const d = createDb('u'); await d.execute?.('DELETE FROM ' + t); }`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E81 template-literal computed member call',
|
||||||
|
src: `import { companies } from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db[\`insert\`](companies).values({}); }`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const CLEAN_CONTROLS: Array<{ name: string; src: string }> = [
|
const CLEAN_CONTROLS: Array<{ name: string; src: string }> = [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user