fix(db): close round-5 review evasions in writer-coverage assertion
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
Unify member-access matching on shared DOT/BRACKET_OPEN/MEMBER_SEG shapes (dot, bracket, ?. and ! forms) across both conduit computations, the write targets, the factory-import clause, and the DI backstop. Treat export-default as an expression at both ends: strip parens/assertions, classify bare-word vs namespace-member chain, on the schema AND capability sides. Add the capability-side export-default ns-member pass (round-5 finding 2). Admit bare SQL qualifier words (TABLE, ONLY, IF EXISTS) between keyword and table name in prong (ii). Controls E54-E68.
This commit is contained in:
@@ -87,7 +87,9 @@
|
|||||||
* function returns, or method extraction. Demonstrated escapes in this
|
* function returns, or method extraction. Demonstrated escapes in this
|
||||||
* class: `const u = this.client.unsafe; u.call(this.client, s)` (the
|
* class: `const u = this.client.unsafe; u.call(this.client, s)` (the
|
||||||
* verb never appears as a member call), an alias chain three or more
|
* verb never appears as a member call), an alias chain three or more
|
||||||
* steps deep, and a helper function that returns a schema symbol.
|
* steps deep, a helper function that returns a schema symbol, and an
|
||||||
|
* export whose expression COMPUTES the value (a ternary, a call result)
|
||||||
|
* rather than naming a binding or member.
|
||||||
* Closing it requires data-flow analysis (a type-checker-backed rewrite,
|
* Closing it requires data-flow analysis (a type-checker-backed rewrite,
|
||||||
* tracked for M4-1b consideration); the counterfactual — flagging every
|
* tracked for M4-1b consideration); the counterfactual — flagging every
|
||||||
* bare identifier call — false-positives on essentially all callback
|
* bare identifier call — false-positives on essentially all callback
|
||||||
@@ -444,6 +446,33 @@ const IMPORT_RE =
|
|||||||
const EXPORT_FROM_RE =
|
const EXPORT_FROM_RE =
|
||||||
/export\s*(type\s+)?(?:\{([^}]*)\}|\*(?:\s*as\s+\w+)?)\s*from\s*['"]([^'"]+)['"]/g;
|
/export\s*(type\s+)?(?:\{([^}]*)\}|\*(?:\s*as\s+\w+)?)\s*from\s*['"]([^'"]+)['"]/g;
|
||||||
|
|
||||||
|
// Member-access shapes, tolerant of optional chaining and non-null
|
||||||
|
// assertions (ns?.companies, ns!.companies, ns?.['companies']) — a `?.` or
|
||||||
|
// `!` between receiver and member must not break a match.
|
||||||
|
const DOT = `\\s*(?:\\?\\.|!\\s*\\.|\\.)\\s*`;
|
||||||
|
const BRACKET_OPEN = `\\s*(?:\\?\\.|!)?\\s*\\[`;
|
||||||
|
const MEMBER_SEG = `(?:${DOT}\\w+|${BRACKET_OPEN}\\s*['"\`]\\w+['"\`]\\s*\\])`;
|
||||||
|
/** Terminal member access matching one of `alt`'s names, dot or bracket form. */
|
||||||
|
const memberTail = (alt: string): string =>
|
||||||
|
`(?:${DOT}(?:${alt})\\b|${BRACKET_OPEN}\\s*['"\`](?:${alt})['"\`]\\s*\\])`;
|
||||||
|
/**
|
||||||
|
* Reduce an exported expression to its core: strip parenthesization and
|
||||||
|
* trailing type assertions (`(companies)`, `companies as unknown as object`)
|
||||||
|
* so `export default <expr>` passes see the binding under the dressing.
|
||||||
|
*/
|
||||||
|
function stripExprDressing(raw: string): string {
|
||||||
|
let expr = raw.trim();
|
||||||
|
for (let prev = ''; prev !== expr; ) {
|
||||||
|
prev = expr;
|
||||||
|
expr = expr
|
||||||
|
.replace(/^\(\s*/, '')
|
||||||
|
.replace(/\s*\)$/, '')
|
||||||
|
.replace(/\s+as\s+[^()]+$/, '')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
return expr;
|
||||||
|
}
|
||||||
|
|
||||||
interface FileFacts {
|
interface FileFacts {
|
||||||
rel: string;
|
rel: string;
|
||||||
code: string;
|
code: string;
|
||||||
@@ -543,7 +572,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}(?:\\s*\\.\\s*\\w+)*\\s*\\.\\s*(?:${CLASS_SYMBOLS.join('|')})\\b`,
|
`export\\s+(?:const|let|var)\\s+(\\w+)\\s*=\\s*${ns}${MEMBER_SEG}*${memberTail(CLASS_SYMBOLS.join('|'))}`,
|
||||||
'g',
|
'g',
|
||||||
),
|
),
|
||||||
)) {
|
)) {
|
||||||
@@ -552,24 +581,28 @@ function computeSchemaConduits(files: FileFacts[], fileSet: Set<string>): Schema
|
|||||||
}
|
}
|
||||||
for (const local of aliases.named) {
|
for (const local of aliases.named) {
|
||||||
for (const m of f.code.matchAll(
|
for (const m of f.code.matchAll(
|
||||||
new RegExp(`export\\s+(?:const|let|var)\\s+(\\w+)\\s*=\\s*${local}\\b`, 'g'),
|
new RegExp(`export\\s+(?:const|let|var)\\s+(\\w+)\\s*=\\s*\\(*\\s*${local}\\b`, 'g'),
|
||||||
)) {
|
)) {
|
||||||
mine.named.add(m[1]!);
|
mine.named.add(m[1]!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The default slot is an export name like any other:
|
// The default slot is an export name like any other, whatever the
|
||||||
// `export default companies;` / `export default mod.companies;`
|
// expression dressing: bare local, parenthesized, type-asserted, or
|
||||||
for (const m of f.code.matchAll(/export\s+default\s+(\w+)\s*;/g)) {
|
// a namespace member chain in dot or bracket form, semicolon or not.
|
||||||
if (aliases.named.includes(m[1]!)) mine.named.add('default');
|
for (const m of f.code.matchAll(/export\s+default\s+([^;\n]+)/g)) {
|
||||||
if (aliases.namespaces.includes(m[1]!)) mine.ns.add('default');
|
const expr = stripExprDressing(m[1]!);
|
||||||
}
|
if (/^\w+$/.test(expr)) {
|
||||||
for (const ns of aliases.namespaces) {
|
if (aliases.named.includes(expr)) mine.named.add('default');
|
||||||
if (
|
if (aliases.namespaces.includes(expr)) mine.ns.add('default');
|
||||||
new RegExp(
|
} else {
|
||||||
`export\\s+default\\s+${ns}(?:\\s*\\.\\s*\\w+)*\\s*\\.\\s*(?:${CLASS_SYMBOLS.join('|')})\\b`,
|
for (const ns of aliases.namespaces) {
|
||||||
).test(f.code)
|
if (
|
||||||
) {
|
new RegExp(`^${ns}${MEMBER_SEG}*${memberTail(CLASS_SYMBOLS.join('|'))}$`).test(expr)
|
||||||
mine.named.add('default');
|
) {
|
||||||
|
mine.named.add('default');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -681,11 +714,11 @@ 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}(?:\\s*\\.\\s*\\w+)*\\s*\\.\\s*(\\w+)\\b`,
|
`(?:const|let|var)\\s+(\\w+)\\s*=\\s*${ns}${MEMBER_SEG}*(?:${DOT}(\\w+)\\b|${BRACKET_OPEN}\\s*['"\`](\\w+)['"\`]\\s*\\])`,
|
||||||
'g',
|
'g',
|
||||||
),
|
),
|
||||||
)) {
|
)) {
|
||||||
if (memberSyms.has(m[2]!)) named.add(m[1]!);
|
if (memberSyms.has((m[2] ?? m[3])!)) named.add(m[1]!);
|
||||||
else namespaces.add(m[1]!);
|
else namespaces.add(m[1]!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -797,9 +830,12 @@ 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(`(?:const|let|var)\\s+(\\w+)\\s*=\\s*${ns}\\s*\\.\\s*(\\w+)\\b`, 'g'),
|
new RegExp(
|
||||||
|
`(?:const|let|var)\\s+(\\w+)\\s*=\\s*${ns}(?:${DOT}(\\w+)\\b|${BRACKET_OPEN}\\s*['"\`](\\w+)['"\`]\\s*\\])`,
|
||||||
|
'g',
|
||||||
|
),
|
||||||
)) {
|
)) {
|
||||||
if (src.has(am[2]!)) locals.add(am[1]!);
|
if (src.has((am[2] ?? am[3])!)) locals.add(am[1]!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (locals.size > 0) {
|
if (locals.size > 0) {
|
||||||
@@ -815,8 +851,32 @@ function computeCapabilityConduits(
|
|||||||
if (new RegExp(`export\\s+(?:const|let|var|function)\\s+${local}\\b`).test(f.code)) {
|
if (new RegExp(`export\\s+(?:const|let|var|function)\\s+${local}\\b`).test(f.code)) {
|
||||||
mine.add(local);
|
mine.add(local);
|
||||||
}
|
}
|
||||||
if (new RegExp(`export\\s+default\\s+${local}\\b`).test(f.code)) {
|
// A derived binding exported under a NEW name re-exports the
|
||||||
mine.add('default');
|
// capability under that name: `export const mk2 = (mk);`
|
||||||
|
for (const dm of f.code.matchAll(
|
||||||
|
new RegExp(`export\\s+(?:const|let|var)\\s+(\\w+)\\s*=\\s*\\(*\\s*${local}\\b`, 'g'),
|
||||||
|
)) {
|
||||||
|
mine.add(dm[1]!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The default slot, under any expression dressing — a local factory
|
||||||
|
// binding (`export default (createDb)`) or a namespace factory member
|
||||||
|
// (`export default mod.createDb`, `export default mod['createDb']`) —
|
||||||
|
// mirrors the schema side's default-export pass.
|
||||||
|
for (const m of f.code.matchAll(/export\s+default\s+([^;\n]+)/g)) {
|
||||||
|
const expr = stripExprDressing(m[1]!);
|
||||||
|
if (/^\w+$/.test(expr)) {
|
||||||
|
if (locals.has(expr)) mine.add('default');
|
||||||
|
} else {
|
||||||
|
for (const [ns, src] of nss) {
|
||||||
|
const t = new RegExp(
|
||||||
|
`^${ns}(?:${DOT}(\\w+)\\b|${BRACKET_OPEN}\\s*['"\`](\\w+)['"\`]\\s*\\])$`,
|
||||||
|
).exec(expr);
|
||||||
|
if (t && src.has((t[1] ?? t[2])!)) {
|
||||||
|
mine.add('default');
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -837,12 +897,13 @@ function hasSymbolImportEdge(code: string, symbols: string[], packageName: strin
|
|||||||
).test(code);
|
).test(code);
|
||||||
const usesSymbol = new RegExp(`\\b(${symbols.join('|')})\\b`).test(code);
|
const usesSymbol = new RegExp(`\\b(${symbols.join('|')})\\b`).test(code);
|
||||||
const dynamicEdge = usesSymbol && new RegExp(`import\\s*\\(\\s*['"]${pkg}['"]\\s*\\)`).test(code);
|
const dynamicEdge = usesSymbol && new RegExp(`import\\s*\\(\\s*['"]${pkg}['"]\\s*\\)`).test(code);
|
||||||
// Namespace form: `import * as ns from '<pkg>'` + `ns.<symbol>` usage.
|
// Namespace form: `import * as ns from '<pkg>'` + `ns.<symbol>` usage
|
||||||
|
// (dot or bracket member, optional-chain/non-null tolerant).
|
||||||
let nsEdge = false;
|
let nsEdge = false;
|
||||||
for (const m of code.matchAll(
|
for (const m of code.matchAll(
|
||||||
new RegExp(`import\\s*\\*\\s*as\\s+(\\w+)\\s*from\\s*['"]${pkg}['"]`, 'g'),
|
new RegExp(`import\\s*\\*\\s*as\\s+(\\w+)\\s*from\\s*['"]${pkg}['"]`, 'g'),
|
||||||
)) {
|
)) {
|
||||||
if (new RegExp(`\\b${m[1]}\\s*\\.\\s*(${symbols.join('|')})\\b`).test(code)) nsEdge = true;
|
if (new RegExp(`\\b${m[1]}${memberTail(symbols.join('|'))}`).test(code)) nsEdge = true;
|
||||||
}
|
}
|
||||||
return staticEdge || dynamicEdge || nsEdge;
|
return staticEdge || dynamicEdge || nsEdge;
|
||||||
}
|
}
|
||||||
@@ -964,9 +1025,9 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
const symAlt = [...aliases.memberSyms].join('|');
|
const symAlt = [...aliases.memberSyms].join('|');
|
||||||
for (const ns of aliases.namespaces) {
|
for (const ns of aliases.namespaces) {
|
||||||
// Allow intermediate property segments (ns.schema.companies — nested
|
// Allow intermediate property segments (ns.schema.companies — nested
|
||||||
// namespace re-exports) and literal computed access (ns['companies']).
|
// namespace re-exports), literal computed access (ns['companies']),
|
||||||
targets.push(`${ns}(?:\\s*\\.\\s*\\w+)*\\s*\\.\\s*(?:${symAlt})\\b`);
|
// and optional-chain/non-null markers (ns?.companies, ns!.companies).
|
||||||
targets.push(`${ns}(?:\\s*\\.\\s*\\w+)*\\s*\\[\\s*['"\`](?:${symAlt})['"\`]\\s*\\]`);
|
targets.push(`${ns}${MEMBER_SEG}*${memberTail(symAlt)}`);
|
||||||
}
|
}
|
||||||
if (targets.length > 0) {
|
if (targets.length > 0) {
|
||||||
// The argument prefix tolerates parenthesization, spread, and array
|
// The argument prefix tolerates parenthesization, spread, and array
|
||||||
@@ -1013,15 +1074,19 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
// The SQL keyword must be ADJACENT to the table name — co-residence
|
// The SQL keyword must be ADJACENT to the table name — co-residence
|
||||||
// anywhere in one span over-matches prose ("pnpm workspaces" plus an
|
// anywhere in one span over-matches prose ("pnpm workspaces" plus an
|
||||||
// unrelated "from" in an embedded doc string is not SQL). Adjacency
|
// unrelated "from" in an embedded doc string is not SQL). Adjacency
|
||||||
// tolerates schema qualification (public.hierarchy_grants), interposed
|
// tolerates schema qualification (public.hierarchy_grants), bare
|
||||||
// block comments, and quoting (including escaped quotes in span text).
|
// qualifier words (TABLE, ONLY, IF EXISTS), interposed block comments,
|
||||||
|
// and quoting (including escaped quotes in span text).
|
||||||
if (!inAllowlist && !isSchemaDefinition) {
|
if (!inAllowlist && !isSchemaDefinition) {
|
||||||
const kw =
|
const kw =
|
||||||
'(?:insert\\s+into|update|delete\\s+from|from|join|truncate(?:\\s+table)?|alter\\s+table|drop\\s+table|references|into|copy|on|lock(?:\\s+table)?)';
|
'(?:insert\\s+into|update|delete\\s+from|from|join|truncate(?:\\s+table)?|alter\\s+table|drop\\s+table|references|into|copy|on|lock(?:\\s+table)?)';
|
||||||
const gap = '(?:\\s|/\\*[\\s\\S]*?\\*/|--[^\\n]*\\n)+';
|
const gap = '(?:\\s|/\\*[\\s\\S]*?\\*/|--[^\\n]*\\n)+';
|
||||||
|
// Bare qualifier words may sit between keyword and table name:
|
||||||
|
// GRANT … ON TABLE t, DELETE FROM ONLY t, DROP TABLE IF EXISTS t.
|
||||||
|
const qual = `(?:(?:table|only|if\\s+exists)${gap})*`;
|
||||||
const q = `(?:\\\\?["'\`])?`;
|
const q = `(?:\\\\?["'\`])?`;
|
||||||
const sqlAdjacentRe = new RegExp(
|
const sqlAdjacentRe = new RegExp(
|
||||||
`\\b${kw}${gap}${q}(?:${q}\\w+${q}\\s*\\.\\s*)?${q}(${CLASS_TABLES.join('|')})\\b`,
|
`\\b${kw}${gap}${qual}${q}(?:${q}\\w+${q}\\s*\\.\\s*)?${q}(${CLASS_TABLES.join('|')})\\b`,
|
||||||
'i',
|
'i',
|
||||||
);
|
);
|
||||||
for (const span of f.spans) {
|
for (const span of f.spans) {
|
||||||
@@ -1066,7 +1131,7 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
hasSymbolImportEdge(code, DB_FACTORY_SYMBOLS, '@mosaicstack/db') ||
|
hasSymbolImportEdge(code, DB_FACTORY_SYMBOLS, '@mosaicstack/db') ||
|
||||||
(aliases.namespaces.length > 0 &&
|
(aliases.namespaces.length > 0 &&
|
||||||
new RegExp(
|
new RegExp(
|
||||||
`\\b(${aliases.namespaces.join('|')})(?:\\s*\\.\\s*\\w+)*\\s*\\.\\s*(createDb|createPgliteDb)\\b`,
|
`\\b(?:${aliases.namespaces.join('|')})${MEMBER_SEG}*${memberTail('createDb|createPgliteDb')}`,
|
||||||
).test(code)) ||
|
).test(code)) ||
|
||||||
// Destructuring a factory symbol OUT of a schema namespace confers
|
// Destructuring a factory symbol OUT of a schema namespace confers
|
||||||
// capability whatever the local rename: const { createDb: mk } = dbns.
|
// capability whatever the local rename: const { createDb: mk } = dbns.
|
||||||
@@ -1102,9 +1167,13 @@ function analyzeFile(f: FileFacts, ctx: AnalysisCtx): Violation[] {
|
|||||||
// DI residual backstop: db/client-shaped receivers fire regardless of
|
// DI residual backstop: db/client-shaped receivers fire regardless of
|
||||||
// detected capability (a handle can arrive by injection).
|
// detected capability (a handle can arrive by injection).
|
||||||
// The receiver may be a dotted name OR a literal bracketed member with
|
// The receiver may be a dotted name OR a literal bracketed member with
|
||||||
// a conventional name: this['db'].query(…).
|
// a conventional name — this['db'].query(…) — and the final member
|
||||||
|
// access tolerates ?. and ! markers (this.db?.query(…)).
|
||||||
for (const m of code.matchAll(
|
for (const m of code.matchAll(
|
||||||
/(?:\b(?:db|database|client|conn|connection|pool|pg|pglite|tx|trx)|\[\s*['"](?:db|database|client|conn|connection|pool|pg|pglite|tx|trx)['"]\s*\])\s*\.\s*(execute|query|unsafe)\s*\(/g,
|
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*\\(`,
|
||||||
|
'g',
|
||||||
|
),
|
||||||
)) {
|
)) {
|
||||||
violations.push({
|
violations.push({
|
||||||
file: rel,
|
file: rel,
|
||||||
@@ -1572,6 +1641,114 @@ describe('hierarchy writer coverage (contract 1 §6.3b)', () => {
|
|||||||
name: 'E53 bracketed conventional receiver',
|
name: 'E53 bracketed conventional 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: 'E54 parenthesized default export of schema symbol',
|
||||||
|
src: `import c from './evasion-mid18.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid18.ts',
|
||||||
|
src: `import { companies } from '@mosaicstack/db';\nexport default (companies);`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E55 bracket-member default export over dynamic namespace',
|
||||||
|
src: `import c from './evasion-mid19.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid19.ts',
|
||||||
|
src: `const mod = await import('@mosaicstack/db');\nexport default mod['companies'];`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E56 type-asserted default export of schema symbol',
|
||||||
|
src: `import c from './evasion-mid20.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(c).values({}); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid20.ts',
|
||||||
|
src: `import { companies } from '@mosaicstack/db';\nexport default companies as unknown as object;`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E57 export-const bracket member of schema namespace',
|
||||||
|
src: `import { co } from './evasion-mid21.js';\nimport { db } from './x.js';\nexport async function f() { await db.insert(co).values({}); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid21.ts',
|
||||||
|
src: `import * as ns from '@mosaicstack/db';\nexport const co = ns['companies'];`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E58 default export of dynamic namespace factory member',
|
||||||
|
src: `import mk from './evasion-mid22.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid22.ts',
|
||||||
|
src: `const mod = await import('@mosaicstack/db');\nexport default mod.createDb;`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E59 default export of static namespace factory member',
|
||||||
|
src: `import mk from './evasion-mid23.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid23.ts',
|
||||||
|
src: `import * as ns from '@mosaicstack/db';\nexport default ns.createDb;`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E60 export-const bracket factory member',
|
||||||
|
src: `import { mk } from './evasion-mid24.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid24.ts',
|
||||||
|
src: `import * as ns from '@mosaicstack/db';\nexport const mk = ns['createDb'];`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E61 parenthesized default export of factory local',
|
||||||
|
src: `import mk from './evasion-mid25.js';\nexport async function f(t: string) { await mk('u').execute('DELETE FROM ' + t); }`,
|
||||||
|
extras: [
|
||||||
|
{
|
||||||
|
rel: 'packages/db/src/evasion-mid25.ts',
|
||||||
|
src: `import { createDb } from '@mosaicstack/db';\nexport default (createDb);`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E62 GRANT ON TABLE class table in SQL span',
|
||||||
|
src: `export const q = 'GRANT SELECT ON TABLE hierarchy_grants TO auditor';`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E63 DROP TABLE IF EXISTS class table in SQL span',
|
||||||
|
src: `export const q = 'DROP TABLE IF EXISTS hierarchy_grants';`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E64 DELETE FROM ONLY class table in SQL span',
|
||||||
|
src: `export const q = 'DELETE FROM ONLY hierarchy_grants WHERE id = $1';`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E65 optional-chained namespace write target',
|
||||||
|
src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns?.companies).values({}); }`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E66 non-null-asserted namespace write target',
|
||||||
|
src: `import * as ns from '@mosaicstack/db';\nimport { db } from './x.js';\nexport async function f() { await db.insert(ns!.companies).values({}); }`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E67 optional-chained DI receiver',
|
||||||
|
src: `export class R { constructor(private db?: { query(s: string): Promise<unknown> }) {}\n async f(t: string) { await this.db?.query('TRUNCATE ' + t); } }`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'E68 optional-chained bracketed DI receiver',
|
||||||
|
src: `export class R { async f(t: string) { await this['db']?.query('TRUNCATE ' + t); } }`,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const CLEAN_CONTROLS: Array<{ name: string; src: string }> = [
|
const CLEAN_CONTROLS: Array<{ name: string; src: string }> = [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user